Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebab8a11cc | ||
|
|
f87b707292 | ||
|
|
420e9218cd | ||
|
|
ad4c240e8b | ||
|
|
6e051c3bfb | ||
|
|
693f7a1df1 | ||
|
|
ad31938c71 | ||
|
|
908038c027 | ||
|
|
4e294f4b18 |
No files matched your search
@@ -27,7 +27,7 @@ To store objects in C++, a type is defined by the template parameters explained
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for `ArrayType` (`std::vector`) and `AllocatorType` (`std::allocator`), the default value for
|
||||
With the default values for `ArrayType` (`std::vector`) and `AllocatorType` (`std::allocator`), the default type for
|
||||
`array_t` is:
|
||||
|
||||
```cpp
|
||||
|
||||
@@ -68,6 +68,14 @@ basic_json(basic_json&& other) noexcept;
|
||||
`std::unordered_map`, `std::multimap`, and `std::unordered_multimap` with a `key_type` compatible to `string_t`
|
||||
and a `value_type` from which a `basic_json` value can be constructed.
|
||||
- **strings**: `string_t`, string literals, and all compatible string containers can be used.
|
||||
|
||||
!!! note "Rvalue containers are moved from"
|
||||
|
||||
If `val` is an rvalue of a compatible array-like or object-like container other than `array_t`/`object_t`
|
||||
(e.g., `json j = std::move(vec);` or `json j(std::vector<std::string>{...});`), the elements are moved into
|
||||
the JSON value instead of copied. Afterwards, the elements of the source container are in a valid but
|
||||
unspecified state (**moved-from**). Keys of associative containers are `const` and therefore still copied.
|
||||
Lvalues and `const` rvalues are copied, and the source is left unchanged.
|
||||
- **numbers**: [`number_integer_t`](number_integer_t.md), [`number_unsigned_t`](number_unsigned_t.md),
|
||||
[`number_float_t`](number_float_t.md), and all convertible number types such as `int`, `size_t`, `int64_t`, `float`
|
||||
or `double` can be used.
|
||||
@@ -236,7 +244,9 @@ basic_json(basic_json&& other) noexcept;
|
||||
2. No-throw guarantee: this constructor never throws exceptions.
|
||||
3. Depends on the called constructor. For types directly supported by the library (i.e., all types for which no
|
||||
`to_json()` function was provided), a strong guarantee holds: if an exception is thrown, there are no changes to any
|
||||
JSON value.
|
||||
JSON value. **Exception:** when `val` is an rvalue array-like or object-like container, its elements are moved one
|
||||
by one; if an exception is thrown in the middle of the conversion (e.g., an allocation failure), the JSON value is
|
||||
unchanged, but the source container may be left partially moved-from.
|
||||
4. Depends on the called constructor. For types directly supported by the library (i.e., all types for which no
|
||||
`to_json()` function was provided), a strong guarantee holds: if an exception is thrown, there are no changes to any
|
||||
JSON value.
|
||||
|
||||
@@ -23,7 +23,7 @@ To store boolean values in C++, a type is defined by the template parameter `Bo
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for `BooleanType` (`#!cpp bool`), the default value for `boolean_t` is `#!cpp bool`.
|
||||
With the default values for `BooleanType` (`#!cpp bool`), the default type for `boolean_t` is `#!cpp bool`.
|
||||
|
||||
### Storage
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ type to use.
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for `NumberFloatType` (`double`), the default value for `number_float_t` is `#!cpp double`.
|
||||
With the default values for `NumberFloatType` (`double`), the default type for `number_float_t` is `#!cpp double`.
|
||||
|
||||
### Default behavior
|
||||
|
||||
@@ -52,8 +52,9 @@ With the default values for `NumberFloatType` (`double`), the default value for
|
||||
> interoperability can be achieved by implementations that expect no more precision or range than these provide, in the
|
||||
> sense that implementations will approximate JSON numbers within the expected precision.
|
||||
|
||||
This implementation does exactly follow this approach, as it uses double precision floating-point numbers. Note that
|
||||
numbers smaller than `-1.79769313486232e+308` or greater than `1.79769313486232e+308` are rejected during parsing with
|
||||
This implementation does exactly follow this approach, as it uses double precision floating-point numbers by default.
|
||||
Note that with the default `double`, numbers smaller than `-1.79769313486232e+308` or greater than
|
||||
`1.79769313486232e+308` are rejected during parsing with
|
||||
[`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406). Non-finite values (NaN and infinity) can
|
||||
only be stored by assigning them directly and are serialized to `null`.
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ to use.
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for `NumberIntegerType` (`std::int64_t`), the default value for `number_integer_t` is
|
||||
With the default values for `NumberIntegerType` (`std::int64_t`), the default type for `number_integer_t` is
|
||||
`#!cpp std::int64_t`.
|
||||
|
||||
### Default behavior
|
||||
|
||||
@@ -32,7 +32,7 @@ the type to use.
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for `NumberUnsignedType` (`std::uint64_t`), the default value for `number_unsigned_t` is
|
||||
With the default values for `NumberUnsignedType` (`std::uint64_t`), the default type for `number_unsigned_t` is
|
||||
`#!cpp std::uint64_t`.
|
||||
|
||||
### Default behavior
|
||||
|
||||
@@ -36,7 +36,7 @@ To store objects in C++, a type is defined by the template parameters described
|
||||
### Default type
|
||||
|
||||
With the default values for `ObjectType` (`std::map`), `StringType` (`std::string`), and `AllocatorType`
|
||||
(`std::allocator`), the default value for `object_t` is:
|
||||
(`std::allocator`), the default type for `object_t` is:
|
||||
|
||||
```cpp
|
||||
// until C++14
|
||||
|
||||
@@ -32,7 +32,7 @@ JSON class into byte-sized characters during deserialization.
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for `StringType` (`std::string`), the default value for `string_t` is `#!cpp std::string`.
|
||||
With the default values for `StringType` (`std::string`), the default type for `string_t` is `#!cpp std::string`.
|
||||
|
||||
### Encoding
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ requirements are collected in [Template Parameter Requirements](template_paramet
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for *ObjectType* (`std::map`), *StringType* (`std::string`), and *AllocatorType* (`std::allocator`), the default value for `object_t` is:
|
||||
With the default values for *ObjectType* (`std::map`), *StringType* (`std::string`), and *AllocatorType* (`std::allocator`), the default type for `object_t` is:
|
||||
|
||||
```cpp
|
||||
std::map<
|
||||
@@ -177,7 +177,7 @@ json j = m;
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for *ArrayType* (`std::vector`) and *AllocatorType* (`std::allocator`), the default value for `array_t` is:
|
||||
With the default values for *ArrayType* (`std::vector`) and *AllocatorType* (`std::allocator`), the default type for `array_t` is:
|
||||
|
||||
```cpp
|
||||
std::vector<
|
||||
@@ -209,7 +209,7 @@ Unicode values are split by the JSON class into byte-sized characters during des
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for *StringType* (`std::string`), the default value for `string_t` is `#!cpp std::string`.
|
||||
With the default values for *StringType* (`std::string`), the default type for `string_t` is `#!cpp std::string`.
|
||||
|
||||
### Encoding
|
||||
|
||||
@@ -234,7 +234,7 @@ String values are stored as pointers in a `basic_json` type. That is, for any ac
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for *BooleanType* (`#!cpp bool`), the default value for `boolean_t` is `#!cpp bool`.
|
||||
With the default values for *BooleanType* (`#!cpp bool`), the default type for `boolean_t` is `#!cpp bool`.
|
||||
|
||||
### Storage
|
||||
|
||||
@@ -252,9 +252,9 @@ This description includes both integer and floating-point numbers. However, C++
|
||||
|
||||
### Default types
|
||||
|
||||
With the default values for *NumberIntegerType* (`std::int64_t`), the default value for `number_integer_t` is `std::int64_t`.
|
||||
With the default values for *NumberUnsignedType* (`std::uint64_t`), the default value for `number_unsigned_t` is `std::uint64_t`.
|
||||
With the default values for *NumberFloatType* (`#!cpp double`), the default value for `number_float_t` is `#!cpp double`.
|
||||
With the default values for *NumberIntegerType* (`std::int64_t`), the default type for `number_integer_t` is `std::int64_t`.
|
||||
With the default values for *NumberUnsignedType* (`std::uint64_t`), the default type for `number_unsigned_t` is `std::uint64_t`.
|
||||
With the default values for *NumberFloatType* (`#!cpp double`), the default type for `number_float_t` is `#!cpp double`.
|
||||
|
||||
### Default behavior
|
||||
|
||||
@@ -281,7 +281,7 @@ As this range is a subrange of the exactly supported range [`INT64_MIN`, `INT64_
|
||||
|
||||
> This specification allows implementations to set limits on the range and precision of numbers accepted. Since software that implements IEEE 754-2008 binary64 (double precision) numbers is generally available and widely used, good interoperability can be achieved by implementations that expect no more precision or range than these provide, in the sense that implementations will approximate JSON numbers within the expected precision.
|
||||
|
||||
This implementation does exactly follow this approach, as it uses double precision floating-point numbers. Note that numbers smaller than `#!c -1.79769313486232e+308` or greater than `#!c 1.79769313486232e+308` are rejected during parsing with [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406). Non-finite values (NaN and infinity) can only be stored by assigning them directly and are serialized to `#!json null`.
|
||||
This implementation does exactly follow this approach, as it uses double precision floating-point numbers by default (see *NumberFloatType* above). Note that with the default `#!cpp double`, numbers smaller than `#!c -1.79769313486232e+308` or greater than `#!c 1.79769313486232e+308` are rejected during parsing with [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406). Non-finite values (NaN and infinity) can only be stored by assigning them directly and are serialized to `#!json null`.
|
||||
|
||||
### Storage
|
||||
|
||||
|
||||
@@ -9,6 +9,15 @@ published on the [GitHub releases page](https://github.com/nlohmann/json/release
|
||||
This documentation is built from the `develop` branch and may describe changes that are not part of a release
|
||||
yet. Their version numbers are followed by an <span class="unreleased-version">unreleased</span> badge.
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Converting an rvalue container to JSON (`json j = std::move(vec);`, `json j = std::map<std::string, std::string>{...};`)
|
||||
now **moves** the elements instead of copying them (e.g., `std::vector<std::string>` with 100,000 strings: 4.7 ms to
|
||||
2.3 ms). Two consequences: the source container is left in a valid but unspecified (moved-from) state, and if an
|
||||
exception is thrown during the conversion, the source may be partially moved-from, so the strong exception guarantee
|
||||
no longer holds for the source of an rvalue conversion (the JSON value itself is unaffected). Lvalues and `const`
|
||||
objects are still copied. See [`basic_json`](../api/basic_json/basic_json.md).
|
||||
|
||||
## v3.12.0 (2025-04-11)
|
||||
|
||||
Fixes bugs found in 3.11.3 and adds several features. All changes are backward-compatible.
|
||||
|
||||
@@ -266,6 +266,22 @@ template<typename ConstructibleArrayType>
|
||||
void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
|
||||
{}
|
||||
|
||||
// append through push_back() where available; containers without it (such as
|
||||
// std::set) fall back to insert(end(), ...), which is what std::inserter did
|
||||
template<typename ConstructibleArrayType, typename ValueType>
|
||||
auto from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<1> /*unused*/)
|
||||
-> decltype(arr.push_back(std::forward<ValueType>(value)), void())
|
||||
{
|
||||
arr.push_back(std::forward<ValueType>(value));
|
||||
}
|
||||
|
||||
template<typename ConstructibleArrayType, typename ValueType>
|
||||
void from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<0> /*unused*/)
|
||||
{
|
||||
using std::end;
|
||||
arr.insert(end(arr), std::forward<ValueType>(value));
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename ConstructibleArrayType,
|
||||
enable_if_t<
|
||||
std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value,
|
||||
@@ -275,17 +291,15 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p
|
||||
j.template get<typename ConstructibleArrayType::value_type>(),
|
||||
void())
|
||||
{
|
||||
using std::end;
|
||||
|
||||
ConstructibleArrayType ret;
|
||||
from_json_array_reserve(ret, j.size(), priority_tag<1> {});
|
||||
std::transform(j.begin(), j.end(),
|
||||
std::inserter(ret, end(ret)), [](const BasicJsonType & i)
|
||||
const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>();
|
||||
from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {});
|
||||
for (const auto& elem : *inner_array)
|
||||
{
|
||||
// get<BasicJsonType>() returns *this, this won't call a from_json
|
||||
// method when value_type is BasicJsonType
|
||||
return i.template get<typename ConstructibleArrayType::value_type>();
|
||||
});
|
||||
from_json_array_append(ret, elem.template get<typename ConstructibleArrayType::value_type>(), priority_tag<1> {});
|
||||
}
|
||||
arr = std::move(ret);
|
||||
}
|
||||
|
||||
@@ -383,9 +397,13 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
||||
ConstructibleObjectType ret;
|
||||
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
|
||||
from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {});
|
||||
using value_type = typename ConstructibleObjectType::value_type;
|
||||
for (const auto& p : *inner_object)
|
||||
{
|
||||
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
|
||||
// the keys of the source are sorted for the usual object types, so
|
||||
// hinting the end makes every insertion constant-time (for containers
|
||||
// that order their keys the same way)
|
||||
ret.insert(ret.end(), value_type(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>()));
|
||||
}
|
||||
obj = std::move(ret);
|
||||
}
|
||||
|
||||
@@ -167,6 +167,17 @@ struct external_constructor<value_t::array>
|
||||
j.replace_value(value_t::array, value);
|
||||
}
|
||||
|
||||
/// an rvalue container: move its elements
|
||||
template<typename BasicJsonType, typename CompatibleArrayType>
|
||||
static void construct_moving(BasicJsonType& j, CompatibleArrayType& arr)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
|
||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(std::make_move_iterator(begin(arr)), std::make_move_iterator(end(arr))));
|
||||
j.replace_value(value_t::array, value);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
static void construct(BasicJsonType& j, const std::vector<bool>& arr)
|
||||
{
|
||||
@@ -217,6 +228,17 @@ struct external_constructor<value_t::object>
|
||||
j.replace_value(value_t::object, value);
|
||||
}
|
||||
|
||||
/// an rvalue container: move its values
|
||||
template<typename BasicJsonType, typename CompatibleObjectType>
|
||||
static void construct_moving(BasicJsonType& j, CompatibleObjectType& obj)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
|
||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(std::make_move_iterator(begin(obj)), std::make_move_iterator(end(obj))));
|
||||
j.replace_value(value_t::object, value);
|
||||
}
|
||||
|
||||
template < typename BasicJsonType, typename CompatibleObjectType,
|
||||
enable_if_t < !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int > = 0 >
|
||||
static void construct(BasicJsonType& j, const CompatibleObjectType& obj)
|
||||
@@ -354,6 +376,66 @@ inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
|
||||
external_constructor<value_t::array>::construct(j, arr);
|
||||
}
|
||||
|
||||
// types that have a dedicated to_json overload and must not be treated as generic containers
|
||||
template<typename T>
|
||||
struct has_dedicated_to_json : std::false_type {};
|
||||
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
template<>
|
||||
struct has_dedicated_to_json<std_fs::path> : std::true_type {};
|
||||
#endif
|
||||
|
||||
#if defined(__cpp_lib_char8_t)
|
||||
template<typename Tr, typename Allocator>
|
||||
struct has_dedicated_to_json<std::basic_string<char8_t, Tr, Allocator>> : std::true_type {};
|
||||
#endif
|
||||
|
||||
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
||||
template<typename T>
|
||||
struct is_range_view_type : is_compatible_range_view<T> {};
|
||||
#else
|
||||
template<typename T>
|
||||
struct is_range_view_type : std::false_type {};
|
||||
#endif
|
||||
|
||||
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||
template<typename T>
|
||||
struct is_enum_keyed_map_type : is_enum_keyed_map<T> {};
|
||||
#else
|
||||
template<typename T>
|
||||
struct is_enum_keyed_map_type : std::false_type {};
|
||||
#endif
|
||||
|
||||
// rvalue (non-const) container: the generic to_json takes its elements by move
|
||||
template<typename T>
|
||||
using is_movable_container_rvalue = conjunction <
|
||||
negation<std::is_lvalue_reference<T>>,
|
||||
negation<std::is_const<T>>,
|
||||
negation<has_dedicated_to_json<T>>,
|
||||
negation<is_basic_json<T>>,
|
||||
negation<is_range_view_type<T>>,
|
||||
negation<is_enum_keyed_map_type<T>>,
|
||||
negation<std::is_same<T, std::vector<bool>>> >;
|
||||
|
||||
// rvalue array-like containers
|
||||
template<typename BasicJsonType, typename T>
|
||||
using is_rvalue_array_to_json = conjunction <
|
||||
is_movable_container_rvalue<T>,
|
||||
negation<std::is_same<T, typename BasicJsonType::array_t>>,
|
||||
negation<std::is_same<T, typename BasicJsonType::binary_t>>,
|
||||
is_compatible_array_type<BasicJsonType, T>,
|
||||
negation<is_compatible_object_type<BasicJsonType, T>>,
|
||||
negation<is_compatible_string_type<BasicJsonType, T>>,
|
||||
negation<is_compatible_binary_type<BasicJsonType, T>> >;
|
||||
|
||||
// rvalue containers: their elements are moved instead of copied
|
||||
template < typename BasicJsonType, typename CompatibleArrayType,
|
||||
enable_if_t < is_rvalue_array_to_json<BasicJsonType, CompatibleArrayType>::value, int > = 0 >
|
||||
inline void to_json(BasicJsonType& j, CompatibleArrayType && arr)
|
||||
{
|
||||
external_constructor<value_t::array>::construct_moving(j, arr);
|
||||
}
|
||||
|
||||
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
||||
template < typename BasicJsonType, typename T,
|
||||
enable_if_t < is_compatible_range_view<std::remove_cvref_t<T>>::value
|
||||
@@ -427,6 +509,20 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
|
||||
}
|
||||
#endif
|
||||
|
||||
// rvalue object-like containers
|
||||
template<typename BasicJsonType, typename T>
|
||||
using is_rvalue_object_to_json = conjunction <
|
||||
is_movable_container_rvalue<T>,
|
||||
negation<std::is_same<T, typename BasicJsonType::object_t>>,
|
||||
is_compatible_object_type<BasicJsonType, T> >;
|
||||
|
||||
template < typename BasicJsonType, typename CompatibleObjectType,
|
||||
enable_if_t < is_rvalue_object_to_json<BasicJsonType, CompatibleObjectType>::value, int > = 0 >
|
||||
inline void to_json(BasicJsonType& j, CompatibleObjectType && obj)
|
||||
{
|
||||
external_constructor<value_t::object>::construct_moving(j, obj);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
|
||||
{
|
||||
|
||||
@@ -674,16 +674,19 @@ class json_sax_dom_callback_parser
|
||||
if (keep && ref_stack.back())
|
||||
{
|
||||
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
||||
const auto it = obj.find(val);
|
||||
if (it != obj.end())
|
||||
// a single lookup: insert the placeholder, or find the existing member
|
||||
const auto inserted = obj.emplace(val, discarded);
|
||||
const auto it = inserted.first;
|
||||
if (!inserted.second)
|
||||
{
|
||||
// this is a duplicate key (legal in JSON); remember its
|
||||
// current value so it can be restored later if the new
|
||||
// value is rejected by the callback, instead of being
|
||||
// erased together with the discarded placeholder
|
||||
duplicate_key_stash.emplace_back(&(it->second), it->second);
|
||||
it->second = discarded;
|
||||
}
|
||||
object_element = &(obj[val] = discarded);
|
||||
object_element = &(it->second);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
#include <iosfwd> // ostream
|
||||
#endif // JSON_NO_IO
|
||||
#include <limits> // max
|
||||
#include <map> // map
|
||||
#include <numeric> // accumulate
|
||||
#include <string> // string
|
||||
#include <utility> // move
|
||||
#include <vector> // vector
|
||||
@@ -69,12 +67,21 @@ class json_pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
|
||||
string_t to_string() const
|
||||
{
|
||||
return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
|
||||
string_t{},
|
||||
[](const string_t& a, const string_t& b)
|
||||
// one buffer: every token adds its escaped form (at least its length) and a slash
|
||||
std::size_t length = 0;
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
return detail::concat<string_t>(a, '/', detail::escape(b));
|
||||
});
|
||||
length += reference_token.size() + 1;
|
||||
}
|
||||
|
||||
string_t result;
|
||||
result.reserve(length);
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
result.append("/", 1);
|
||||
detail::escape_into(result, reference_token);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief return a string representation of the JSON pointer
|
||||
@@ -129,21 +136,29 @@ class json_pointer
|
||||
friend json_pointer operator/(const json_pointer& lhs,
|
||||
const json_pointer& rhs)
|
||||
{
|
||||
return json_pointer(lhs) /= rhs;
|
||||
json_pointer result;
|
||||
result.reference_tokens.reserve(lhs.reference_tokens.size() + rhs.reference_tokens.size());
|
||||
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
||||
result.reference_tokens.insert(result.reference_tokens.end(), rhs.reference_tokens.begin(), rhs.reference_tokens.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
||||
friend json_pointer operator/(const json_pointer& lhs, string_t token) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
return json_pointer(lhs) /= std::move(token);
|
||||
json_pointer result;
|
||||
result.reference_tokens.reserve(lhs.reference_tokens.size() + 1);
|
||||
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
||||
result.reference_tokens.push_back(std::move(token));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
||||
friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx)
|
||||
{
|
||||
return json_pointer(lhs) /= array_idx;
|
||||
return lhs / detail::to_string<string_t>(array_idx);
|
||||
}
|
||||
|
||||
/// @brief returns the parent of this JSON pointer
|
||||
@@ -155,8 +170,8 @@ class json_pointer
|
||||
return *this;
|
||||
}
|
||||
|
||||
json_pointer res = *this;
|
||||
res.pop_back();
|
||||
json_pointer res;
|
||||
res.reference_tokens = std::vector<string_t>(reference_tokens.begin(), reference_tokens.end() - 1);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -245,14 +260,14 @@ class json_pointer
|
||||
|
||||
private:
|
||||
/// @throw out_of_range.405 always
|
||||
JSON_HEDLEY_NO_RETURN static void throw_no_parent()
|
||||
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_no_parent()
|
||||
{
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr));
|
||||
}
|
||||
|
||||
/// @throw out_of_range.404 always
|
||||
template<typename BasicJsonContext>
|
||||
JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context)
|
||||
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context)
|
||||
{
|
||||
static_cast<void>(reference_token); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(context);
|
||||
@@ -305,6 +320,28 @@ class json_pointer
|
||||
return array_index_status::not_a_number;
|
||||
}
|
||||
|
||||
// fast path: up to 18 decimal digits cannot overflow unsigned long long or
|
||||
// size_type (9 digits for a 32-bit size_type), so strtoull and errno are not needed
|
||||
constexpr std::size_t max_fast_digits = sizeof(size_type) >= 8 ? 18 : 9;
|
||||
if (!s.empty() && s.size() <= max_fast_digits)
|
||||
{
|
||||
size_type acc = 0;
|
||||
for (std::size_t i = 0; i < s.size(); ++i)
|
||||
{
|
||||
const char c = s[i];
|
||||
if (JSON_HEDLEY_UNLIKELY(c < '0' || c > '9'))
|
||||
{
|
||||
break; // not a plain number: let strtoull decide
|
||||
}
|
||||
acc = static_cast<size_type>(acc * 10 + static_cast<size_type>(c - '0'));
|
||||
if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1))
|
||||
{
|
||||
idx = acc;
|
||||
return array_index_status::ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* p = s.data();
|
||||
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||
errno = 0; // strtoull doesn't reset errno
|
||||
@@ -361,6 +398,83 @@ class json_pointer
|
||||
return idx;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief compare with a JSON pointer string without splitting it into tokens
|
||||
|
||||
@throw parse_error.107 if the string is not empty or begins with '/'
|
||||
@throw parse_error.108 if character '~' is not followed by '0' or '1'
|
||||
*/
|
||||
bool equals_string(const string_t& reference_string) const
|
||||
{
|
||||
if (reference_string.empty())
|
||||
{
|
||||
return reference_tokens.empty();
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
|
||||
}
|
||||
|
||||
// the whole string is validated, whether or not it matches
|
||||
const std::size_t size = reference_string.size();
|
||||
for (std::size_t pos = reference_string.find_first_of('~');
|
||||
pos != string_t::npos;
|
||||
pos = reference_string.find_first_of('~', pos + 1))
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(pos == size - 1 || (reference_string[pos + 1] != '0' && reference_string[pos + 1] != '1')))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
// compare token by token, unescaping the string on the fly
|
||||
std::size_t pos = 0; // the slash in front of the next token
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
if (pos >= size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t i = pos + 1;
|
||||
std::size_t j = 0;
|
||||
const std::size_t token_size = reference_token.size();
|
||||
while (i < size && reference_string[i] != '/')
|
||||
{
|
||||
char c = reference_string[i++];
|
||||
if (c == '~')
|
||||
{
|
||||
c = (reference_string[i++] == '0') ? '~' : '/';
|
||||
}
|
||||
if (j == token_size || reference_token[j] != c)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
++j;
|
||||
}
|
||||
if (j != token_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
pos = i;
|
||||
}
|
||||
return pos == size;
|
||||
}
|
||||
|
||||
/// @brief object member access with a key that is only copied if it is inserted
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
|
||||
{
|
||||
auto& members = *object.m_data.m_value.object;
|
||||
const auto it = members.find(key);
|
||||
if (it != members.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return object.operator[](key);
|
||||
}
|
||||
|
||||
JSON_PRIVATE_UNLESS_TESTED:
|
||||
json_pointer top() const
|
||||
{
|
||||
@@ -384,84 +498,141 @@ class json_pointer
|
||||
the result independent of the order in which the flattened object is
|
||||
iterated, which is unspecified for some object types.
|
||||
|
||||
The prefixes form a tree and are numbered, so each of them is stored only
|
||||
once (as a node) rather than as a copy of all of its reference tokens.
|
||||
The prefixes form a tree and are numbered (number 0 is the empty prefix),
|
||||
so each of them is stored only once (as a node) rather than as a copy of
|
||||
all of its reference tokens. The tree does not own the reference tokens:
|
||||
it refers to the tokens of the parsed keys, which must outlive it.
|
||||
*/
|
||||
struct prefix_tree
|
||||
{
|
||||
// children[id] maps a reference token to the number of the prefix
|
||||
// extended by that token; number 0 is the empty prefix
|
||||
std::vector<std::map<string_t, std::size_t>> children;
|
||||
// open-addressing hash table from (parent, last reference token) to
|
||||
// the number of the node; empty slots have token == nullptr
|
||||
struct slot
|
||||
{
|
||||
std::size_t hash;
|
||||
std::size_t parent;
|
||||
const string_t* token;
|
||||
std::size_t id;
|
||||
|
||||
slot() noexcept
|
||||
: hash(0), parent(0), token(nullptr), id(0)
|
||||
{}
|
||||
|
||||
slot(std::size_t h, std::size_t p, const string_t* t, std::size_t i) noexcept
|
||||
: hash(h), parent(p), token(t), id(i)
|
||||
{}
|
||||
};
|
||||
|
||||
std::vector<slot> slots;
|
||||
std::size_t used = 0;
|
||||
|
||||
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
|
||||
{
|
||||
std::size_t h = parent * static_cast<std::size_t>(0x9E3779B97F4A7C15ULL);
|
||||
for (std::size_t i = 0; i < token.size(); ++i)
|
||||
{
|
||||
h = (h ^ static_cast<unsigned char>(token[i])) * static_cast<std::size_t>(0x100000001B3ULL);
|
||||
}
|
||||
return h ^ (h >> 29u);
|
||||
}
|
||||
|
||||
void grow()
|
||||
{
|
||||
std::vector<slot> old(slots.size() * 2);
|
||||
old.swap(slots);
|
||||
const std::size_t mask = slots.size() - 1;
|
||||
for (const auto& s : old)
|
||||
{
|
||||
if (s.token != nullptr)
|
||||
{
|
||||
std::size_t i = s.hash & mask;
|
||||
while (slots[i].token != nullptr)
|
||||
{
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
slots[i] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parent[id] is the number of the prefix without its last token
|
||||
std::vector<std::size_t> parent;
|
||||
// is_array[id] is true iff some flattened key has the reference token
|
||||
// 0 directly below the prefix with number id
|
||||
std::vector<bool> is_array;
|
||||
|
||||
// start with the empty prefix only
|
||||
prefix_tree()
|
||||
: children(1)
|
||||
: slots(16)
|
||||
, parent(1, 0)
|
||||
, is_array(1, false)
|
||||
{}
|
||||
|
||||
// return the number of the prefix with number id extended by
|
||||
// reference_token, adding it if it is new
|
||||
std::size_t add_child(std::size_t id, string_t&& reference_token)
|
||||
std::size_t add_child(std::size_t id, const string_t& reference_token)
|
||||
{
|
||||
const std::size_t hash = hash_of(id, reference_token);
|
||||
const std::size_t mask = slots.size() - 1;
|
||||
std::size_t i = hash & mask;
|
||||
while (slots[i].token != nullptr)
|
||||
{
|
||||
if (slots[i].hash == hash && slots[i].parent == id && *slots[i].token == reference_token)
|
||||
{
|
||||
return slots[i].id;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
|
||||
// new prefix
|
||||
const std::size_t child = parent.size();
|
||||
slots[i] = slot(hash, id, &reference_token, child);
|
||||
if (reference_token == "0")
|
||||
{
|
||||
is_array[id] = true;
|
||||
}
|
||||
parent.push_back(id);
|
||||
is_array.push_back(false);
|
||||
|
||||
// read the number before the emplace_back below, which may
|
||||
// reallocate children and invalidate the iterator
|
||||
const std::size_t next = children.size();
|
||||
const auto inserted = children[id].emplace(std::move(reference_token), next);
|
||||
const std::size_t child = inserted.first->second;
|
||||
if (inserted.second)
|
||||
// keep the load factor below 1/2
|
||||
if (++used * 2 > slots.size())
|
||||
{
|
||||
children.emplace_back();
|
||||
is_array.push_back(false);
|
||||
grow();
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
// return the number of the prefix with number id extended by
|
||||
// reference_token, which must have been added before
|
||||
std::size_t find_child(std::size_t id, const string_t& reference_token) const
|
||||
{
|
||||
const auto it = children[id].find(reference_token);
|
||||
JSON_ASSERT(it != children[id].end());
|
||||
return it->second;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief create and return a reference to the pointed to value
|
||||
|
||||
Complexity: Linear in the number of reference tokens (times the logarithm
|
||||
of the number of siblings for the prefix lookup).
|
||||
@param[in] j the value to create the pointed to value in
|
||||
@param[in] tokens the reference tokens of the pointer
|
||||
@param[in] prefixes prefixes[i] is the number in @a tree of the prefix
|
||||
consisting of the first i reference tokens
|
||||
|
||||
Complexity: Linear in the number of reference tokens.
|
||||
|
||||
@throw parse_error.106 if an array index begins with '0'
|
||||
@throw parse_error.109 if array index is not a number
|
||||
@throw type_error.313 if value cannot be unflattened
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
BasicJsonType& get_and_create(BasicJsonType& j, const prefix_tree& tree) const
|
||||
static BasicJsonType& get_and_create(BasicJsonType& j, const std::vector<string_t>& tokens,
|
||||
const std::vector<std::size_t>& prefixes, const prefix_tree& tree)
|
||||
{
|
||||
auto* result = &j;
|
||||
|
||||
// the number of the prefix consumed so far; used to look up whether
|
||||
// the value to be created below is an array or an object
|
||||
std::size_t id = 0;
|
||||
|
||||
// in case no reference tokens exist, return a reference to the JSON value
|
||||
// j which will be overwritten by a primitive value
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
for (std::size_t i = 0; i < tokens.size(); ++i)
|
||||
{
|
||||
const auto& reference_token = tokens[i];
|
||||
switch (result->type())
|
||||
{
|
||||
case detail::value_t::null:
|
||||
{
|
||||
if (tree.is_array[id])
|
||||
if (tree.is_array[prefixes[i]])
|
||||
{
|
||||
// some reference token below this position is 0, so the
|
||||
// value is an array
|
||||
@@ -478,7 +649,7 @@ class json_pointer
|
||||
case detail::value_t::object:
|
||||
{
|
||||
// create an entry in the object
|
||||
result = &result->operator[](reference_token);
|
||||
result = &object_member(*result, reference_token);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -505,8 +676,6 @@ class json_pointer
|
||||
default:
|
||||
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j));
|
||||
}
|
||||
|
||||
id = tree.find_child(id, reference_token);
|
||||
}
|
||||
|
||||
return *result;
|
||||
@@ -558,7 +727,7 @@ class json_pointer
|
||||
case detail::value_t::object:
|
||||
{
|
||||
// use unchecked object access
|
||||
ptr = &ptr->operator[](reference_token);
|
||||
ptr = &object_member(*ptr, reference_token);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -608,8 +777,13 @@ class json_pointer
|
||||
{
|
||||
case detail::value_t::object:
|
||||
{
|
||||
// note: at performs range check
|
||||
ptr = &ptr->at(reference_token);
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
{
|
||||
// at() throws out_of_range.403 with the usual message
|
||||
ptr->at(reference_token);
|
||||
}
|
||||
ptr = &it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -630,7 +804,7 @@ class json_pointer
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat(
|
||||
"array index ", std::to_string(idx), " is out of range"), ptr));
|
||||
}
|
||||
ptr = &ptr->operator[](idx);
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -678,7 +852,9 @@ class json_pointer
|
||||
{
|
||||
// use unchecked object access; the const operator[]
|
||||
// asserts that the key exists
|
||||
ptr = &ptr->operator[](reference_token);
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
JSON_ASSERT(it != ptr->m_data.m_value.object->end());
|
||||
ptr = &it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -692,7 +868,9 @@ class json_pointer
|
||||
|
||||
// use unchecked array access; the const operator[]
|
||||
// asserts that the index exists
|
||||
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
|
||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||
JSON_ASSERT(idx < ptr->m_data.m_value.array->size());
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -733,12 +911,12 @@ class json_pointer
|
||||
{
|
||||
case detail::value_t::object:
|
||||
{
|
||||
const auto it = ptr->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->end()))
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &*it;
|
||||
ptr = &it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -775,7 +953,7 @@ class json_pointer
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &ptr->operator[](idx);
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -808,13 +986,14 @@ class json_pointer
|
||||
{
|
||||
case detail::value_t::object:
|
||||
{
|
||||
if (!ptr->contains(reference_token))
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
if (it == ptr->m_data.m_value.object->end())
|
||||
{
|
||||
// we did not find the key in the object
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = &ptr->operator[](reference_token);
|
||||
ptr = &it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -836,13 +1015,13 @@ class json_pointer
|
||||
return false;
|
||||
}
|
||||
|
||||
if (idx >= ptr->size())
|
||||
if (idx >= ptr->m_data.m_value.array->size())
|
||||
{
|
||||
// index out of range
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = &ptr->operator[](idx);
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -892,6 +1071,15 @@ class json_pointer
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
|
||||
}
|
||||
|
||||
// every slash starts a reference token
|
||||
const std::size_t size = reference_string.size();
|
||||
std::size_t token_count = 0;
|
||||
for (std::size_t i = 0; i < size; ++i)
|
||||
{
|
||||
token_count += (reference_string[i] == '/') ? 1 : 0;
|
||||
}
|
||||
result.reserve(token_count);
|
||||
|
||||
// extract the reference tokens:
|
||||
// - slash: position of the last read slash (or end of string)
|
||||
// - start: position after the previous slash
|
||||
@@ -913,31 +1101,52 @@ class json_pointer
|
||||
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
|
||||
auto reference_token = string_t(reference_string.data() + start, count);
|
||||
|
||||
// check reference tokens are properly escaped
|
||||
for (std::size_t pos = reference_token.find_first_of('~');
|
||||
pos != string_t::npos;
|
||||
pos = reference_token.find_first_of('~', pos + 1))
|
||||
// check reference tokens are properly escaped; a token without
|
||||
// a '~' (the common case) needs neither check nor unescaping
|
||||
std::size_t pos = reference_token.find_first_of('~');
|
||||
if (pos != string_t::npos)
|
||||
{
|
||||
JSON_ASSERT(reference_token[pos] == '~');
|
||||
|
||||
// ~ must be followed by 0 or 1
|
||||
if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
|
||||
(reference_token[pos + 1] != '0' &&
|
||||
reference_token[pos + 1] != '1')))
|
||||
for (; pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
||||
JSON_ASSERT(reference_token[pos] == '~');
|
||||
|
||||
// ~ must be followed by 0 or 1
|
||||
if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
|
||||
(reference_token[pos + 1] != '0' &&
|
||||
reference_token[pos + 1] != '1')))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
detail::unescape(reference_token);
|
||||
}
|
||||
|
||||
// finally, store the reference token
|
||||
detail::unescape(reference_token);
|
||||
result.push_back(reference_token);
|
||||
result.push_back(std::move(reference_token));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
// Add the entry (key, v) to the object `obj`. Flattened paths are pairwise
|
||||
// distinct, so objects that can append a key known to be absent
|
||||
// (ordered_map) skip the lookup, which is linear and would make flatten
|
||||
// quadratic; all other object types use operator[].
|
||||
template<typename ObjectType, typename ValueType>
|
||||
static auto add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, int /*unused*/)
|
||||
-> decltype(obj.emplace_new_key(key, std::forward<ValueType>(v)), void())
|
||||
{
|
||||
obj.emplace_new_key(key, std::forward<ValueType>(v));
|
||||
}
|
||||
|
||||
template<typename ObjectType, typename ValueType>
|
||||
static void add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, ...)
|
||||
{
|
||||
obj[key] = std::forward<ValueType>(v);
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] reference_string the reference string to the current value
|
||||
@param[in] value the value to consider
|
||||
@@ -982,7 +1191,8 @@ class json_pointer
|
||||
|
||||
// flatten `v`, whose path is `path`: primitives and empty containers
|
||||
// are added to the result right away; other containers get a frame
|
||||
const auto enter = [&stack, &path, &result](const BasicJsonType & v)
|
||||
auto& object = *result.m_data.m_value.object;
|
||||
const auto enter = [&stack, &path, &object](const BasicJsonType & v)
|
||||
{
|
||||
switch (v.type())
|
||||
{
|
||||
@@ -991,7 +1201,7 @@ class json_pointer
|
||||
if (v.m_data.m_value.array->empty())
|
||||
{
|
||||
// flatten empty array as null
|
||||
result[path] = nullptr;
|
||||
add_flattened(object, path, nullptr, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1005,7 +1215,7 @@ class json_pointer
|
||||
if (v.m_data.m_value.object->empty())
|
||||
{
|
||||
// flatten empty object as null
|
||||
result[path] = nullptr;
|
||||
add_flattened(object, path, nullptr, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1025,7 +1235,7 @@ class json_pointer
|
||||
default:
|
||||
{
|
||||
// add a primitive value with its reference string
|
||||
result[path] = v;
|
||||
add_flattened(object, path, v, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1071,6 +1281,10 @@ class json_pointer
|
||||
enter(it->second);
|
||||
}
|
||||
}
|
||||
|
||||
// the entries were added without going through basic_json, so the
|
||||
// parent pointers (JSON_DIAGNOSTICS) have to be set now
|
||||
result.set_parents();
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1094,21 +1308,53 @@ class json_pointer
|
||||
|
||||
BasicJsonType result;
|
||||
|
||||
// parse every key once; the reference tokens are kept for the second
|
||||
// pass (the vectors are never resized, so the tree may refer to them)
|
||||
std::vector<std::vector<string_t>> keys;
|
||||
keys.reserve(value.m_data.m_value.object->size());
|
||||
for (const auto& element : *value.m_data.m_value.object)
|
||||
{
|
||||
json_pointer ptr(element.first);
|
||||
keys.push_back(std::move(ptr.reference_tokens));
|
||||
}
|
||||
|
||||
// collect the pointer prefixes that have a reference token 0 among
|
||||
// their children; the values below them are arrays, all others are
|
||||
// objects (see prefix_tree)
|
||||
prefix_tree tree;
|
||||
for (const auto& element : *value.m_data.m_value.object)
|
||||
// the number of the prefix with i + 1 tokens of the previous key; keys
|
||||
// are often sorted, so most of the prefix is the same as for the
|
||||
// previous key and need not be looked up again
|
||||
std::vector<std::size_t> path;
|
||||
const std::vector<string_t>* previous = nullptr;
|
||||
// the number of the complete prefix (i.e., the key) of each key
|
||||
std::vector<std::size_t> leaves;
|
||||
leaves.reserve(keys.size());
|
||||
for (const auto& tokens : keys)
|
||||
{
|
||||
json_pointer ptr(element.first);
|
||||
std::size_t id = 0;
|
||||
for (auto& reference_token : ptr.reference_tokens)
|
||||
std::size_t common = 0;
|
||||
if (previous != nullptr)
|
||||
{
|
||||
id = tree.add_child(id, std::move(reference_token));
|
||||
const std::size_t limit = (std::min)(tokens.size(), previous->size());
|
||||
while (common < limit && tokens[common] == (*previous)[common])
|
||||
{
|
||||
++common;
|
||||
}
|
||||
}
|
||||
path.resize(common);
|
||||
std::size_t id = common == 0 ? 0 : path[common - 1];
|
||||
for (std::size_t i = common; i < tokens.size(); ++i)
|
||||
{
|
||||
id = tree.add_child(id, tokens[i]);
|
||||
path.push_back(id);
|
||||
}
|
||||
leaves.push_back(id);
|
||||
previous = &tokens;
|
||||
}
|
||||
|
||||
// iterate the JSON object values
|
||||
std::vector<std::size_t> prefixes;
|
||||
std::size_t key_index = 0;
|
||||
for (const auto& element : *value.m_data.m_value.object)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))
|
||||
@@ -1116,11 +1362,21 @@ class json_pointer
|
||||
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_value_not_primitive, "values in object must be primitive", &element.second));
|
||||
}
|
||||
|
||||
const auto& tokens = keys[key_index];
|
||||
// number the prefixes of this key from its last one to the first
|
||||
prefixes.resize(tokens.size());
|
||||
for (std::size_t i = tokens.size(), id = leaves[key_index]; i > 0; --i)
|
||||
{
|
||||
id = tree.parent[id];
|
||||
prefixes[i - 1] = id;
|
||||
}
|
||||
++key_index;
|
||||
|
||||
// Assign the value to the reference pointed to by JSON pointer. Note
|
||||
// that if the JSON pointer is "" (i.e., points to the whole value),
|
||||
// function get_and_create returns a reference to the result itself.
|
||||
// An assignment will then create a primitive value.
|
||||
json_pointer(element.first).get_and_create(result, tree) = element.second;
|
||||
get_and_create(result, tokens, prefixes, tree) = element.second;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -1159,7 +1415,7 @@ class json_pointer
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return *this == json_pointer(rhs);
|
||||
return equals_string(rhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1243,7 +1499,7 @@ inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return lhs == json_pointer<RefStringTypeLhs>(rhs);
|
||||
return lhs.equals_string(rhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1256,7 +1512,7 @@ inline bool operator==(const StringType& lhs,
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return json_pointer<RefStringTypeRhs>(lhs) == rhs;
|
||||
return rhs.equals_string(lhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <utility> // move
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
|
||||
@@ -24,39 +25,46 @@ namespace detail
|
||||
* Note the order of escaping "~" to "~0" and "/" to "~1" is important.
|
||||
*
|
||||
* The string is rebuilt in a single pass, appending whole runs between the
|
||||
* characters that need escaping. Scanning with find_first_of() keeps the
|
||||
* common case -- nothing to escape -- as fast as a single search, while
|
||||
* repeated replace() calls would move the tail of the string once per
|
||||
* escaped character.
|
||||
* characters that need escaping, while repeated replace() calls would move
|
||||
* the tail of the string once per escaped character. @ref escape_into appends
|
||||
* the escaped string to an existing one.
|
||||
*/
|
||||
template<typename StringType>
|
||||
inline void escape_into(StringType& out, const StringType& s)
|
||||
{
|
||||
const std::size_t size = s.size();
|
||||
std::size_t run = 0;
|
||||
for (std::size_t i = 0; i < size; ++i)
|
||||
{
|
||||
const char c = s[i];
|
||||
if (c == '~' || c == '/')
|
||||
{
|
||||
out.append(s.data() + run, i - run);
|
||||
out.append(c == '~' ? "~0" : "~1", 2);
|
||||
run = i + 1;
|
||||
}
|
||||
}
|
||||
out.append(s.data() + run, size - run);
|
||||
}
|
||||
|
||||
template<typename StringType>
|
||||
inline StringType escape(const StringType& s)
|
||||
{
|
||||
auto next_special = [&s](std::size_t from)
|
||||
// a single scan for both special characters; nothing to escape is the common case
|
||||
const std::size_t size = s.size();
|
||||
std::size_t i = 0;
|
||||
while (i < size && s[i] != '~' && s[i] != '/')
|
||||
{
|
||||
const auto tilde = s.find_first_of('~', from);
|
||||
const auto slash = s.find_first_of('/', from);
|
||||
return tilde < slash ? tilde : slash; // npos is the largest value
|
||||
};
|
||||
|
||||
auto pos = next_special(0);
|
||||
if (pos == StringType::npos)
|
||||
++i;
|
||||
}
|
||||
if (i == size)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
StringType result;
|
||||
result.reserve(s.size() + 2);
|
||||
|
||||
std::size_t run = 0;
|
||||
while (pos != StringType::npos)
|
||||
{
|
||||
result.append(s.data() + run, pos - run);
|
||||
result.append(s[pos] == '~' ? "~0" : "~1", 2);
|
||||
run = pos + 1;
|
||||
pos = next_special(run);
|
||||
}
|
||||
result.append(s.data() + run, s.size() - run);
|
||||
result.reserve(size + 2);
|
||||
escape_into(result, s);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -101,7 +109,7 @@ inline void unescape(StringType& s)
|
||||
pos = s.find_first_of('~', run);
|
||||
}
|
||||
result.append(s.data() + run, s.size() - run);
|
||||
s = result;
|
||||
s = std::move(result);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
+77
-68
@@ -1079,51 +1079,64 @@ public:
|
||||
return next;
|
||||
}
|
||||
|
||||
/// @brief the capacity of the stored array, or unknown_size()
|
||||
/// @brief the capacity of the stored array or object, or unknown_size()
|
||||
/// Only JSON_DIAGNOSTICS uses the value, to detect a reallocation that
|
||||
/// would invalidate the parent pointers. Array types that do not have a
|
||||
/// capacity() member function report unknown_size(), which is treated as
|
||||
/// "the elements may have moved".
|
||||
/// would invalidate the parent pointers. Containers that do not have a
|
||||
/// capacity() member function report unknown_size() if their elements may
|
||||
/// move (arrays, ordered_map) and 0 if they never do (std::map).
|
||||
#if JSON_DIAGNOSTICS
|
||||
template < typename A = array_t, detail::enable_if_t < detail::has_capacity<A>::value, int > = 0 >
|
||||
std::size_t array_capacity() const noexcept
|
||||
template < typename C, detail::enable_if_t < detail::has_capacity<C>::value, int > = 0 >
|
||||
static std::size_t capacity_of(const C& container) noexcept
|
||||
{
|
||||
return m_data.m_value.array->capacity();
|
||||
return container.capacity();
|
||||
}
|
||||
|
||||
template < typename A = array_t, detail::enable_if_t < !detail::has_capacity<A>::value, int > = 0 >
|
||||
std::size_t array_capacity() const noexcept
|
||||
template < typename C, detail::enable_if_t < !detail::has_capacity<C>::value, int > = 0 >
|
||||
static std::size_t capacity_of(const C& /*container*/) noexcept
|
||||
{
|
||||
return detail::unknown_size();
|
||||
return std::is_same<C, array_t>::value || detail::is_ordered_map<C>::value ? detail::unknown_size() : 0;
|
||||
}
|
||||
|
||||
std::size_t container_capacity() const noexcept
|
||||
{
|
||||
return is_array() ? capacity_of(*m_data.m_value.array) : capacity_of(*m_data.m_value.object);
|
||||
}
|
||||
#else
|
||||
static constexpr std::size_t array_capacity() noexcept
|
||||
static constexpr std::size_t container_capacity() noexcept
|
||||
{
|
||||
return detail::unknown_size();
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief set the parent of a value that has just been added to an array
|
||||
/// @param j the added value
|
||||
/// @param old_capacity the value @ref array_capacity() returned before the
|
||||
/// @brief set the parent of a value that has just been added to an array or object
|
||||
/// @param j the added (or looked-up) value
|
||||
/// @param old_capacity the value @ref container_capacity() returned before the
|
||||
/// insertion
|
||||
reference set_parent_after_array_insert(reference j, std::size_t old_capacity)
|
||||
reference set_parent_after_insert(reference j, std::size_t old_capacity)
|
||||
{
|
||||
#if JSON_DIAGNOSTICS
|
||||
// see https://github.com/nlohmann/json/issues/2838
|
||||
JSON_ASSERT(type() == value_t::array);
|
||||
if (JSON_HEDLEY_UNLIKELY(old_capacity == detail::unknown_size()
|
||||
|| array_capacity() != old_capacity))
|
||||
|| container_capacity() != old_capacity))
|
||||
{
|
||||
// the capacity has changed, or the array type does not let us tell:
|
||||
// the elements may have moved, so update all parents
|
||||
// the capacity has changed, or the container type does not let us
|
||||
// tell: the elements may have moved, so update all parents
|
||||
set_parents();
|
||||
return j;
|
||||
}
|
||||
#else
|
||||
static_cast<void>(old_capacity);
|
||||
#endif
|
||||
return set_parent(j);
|
||||
return set_parent_at(j);
|
||||
}
|
||||
|
||||
/// @brief set the parent of an element accessed via at(); never moves elements
|
||||
reference set_parent_at(reference j) noexcept
|
||||
{
|
||||
#if JSON_DIAGNOSTICS
|
||||
j.m_parent = this;
|
||||
#endif
|
||||
return j;
|
||||
}
|
||||
|
||||
reference set_parent(reference j)
|
||||
@@ -2557,7 +2570,6 @@ public:
|
||||
copy_leaf_value(other, *this);
|
||||
}
|
||||
|
||||
set_parents();
|
||||
assert_invariant();
|
||||
}
|
||||
|
||||
@@ -3445,7 +3457,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/at/
|
||||
reference at(size_type idx)
|
||||
{
|
||||
return set_parent(array_at(*this, idx));
|
||||
return set_parent_at(array_at(*this, idx));
|
||||
}
|
||||
|
||||
/// @brief access specified array element with bounds checking
|
||||
@@ -3459,7 +3471,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/at/
|
||||
reference at(const typename object_t::key_type& key)
|
||||
{
|
||||
return set_parent(object_at(*this, key));
|
||||
return set_parent_at(object_at(*this, key));
|
||||
}
|
||||
|
||||
/// @brief access specified object element with bounds checking
|
||||
@@ -3468,7 +3480,7 @@ public:
|
||||
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
|
||||
reference at(KeyType && key)
|
||||
{
|
||||
return set_parent(object_at(*this, std::forward<KeyType>(key)));
|
||||
return set_parent_at(object_at(*this, std::forward<KeyType>(key)));
|
||||
}
|
||||
|
||||
/// @brief access specified object element with bounds checking
|
||||
@@ -3513,13 +3525,13 @@ public:
|
||||
#if JSON_DIAGNOSTICS
|
||||
// remember array size & capacity before resizing
|
||||
const auto old_size = m_data.m_value.array->size();
|
||||
const auto old_capacity = array_capacity();
|
||||
const auto old_capacity = container_capacity();
|
||||
#endif
|
||||
m_data.m_value.array->resize(idx + 1);
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
if (JSON_HEDLEY_UNLIKELY(old_capacity == detail::unknown_size()
|
||||
|| array_capacity() != old_capacity))
|
||||
|| container_capacity() != old_capacity))
|
||||
{
|
||||
// capacity has changed: update all parents
|
||||
set_parents();
|
||||
@@ -3566,8 +3578,9 @@ public:
|
||||
// operator[] only works for objects
|
||||
if (JSON_HEDLEY_LIKELY(is_object()))
|
||||
{
|
||||
const auto old_capacity = container_capacity();
|
||||
auto result = m_data.m_value.object->emplace(std::move(key), nullptr);
|
||||
return set_parent(result.first->second);
|
||||
return set_parent_after_insert(result.first->second, old_capacity);
|
||||
}
|
||||
|
||||
throw_subscript_wrong_type("string");
|
||||
@@ -3617,8 +3630,9 @@ public:
|
||||
// operator[] only works for objects
|
||||
if (JSON_HEDLEY_LIKELY(is_object()))
|
||||
{
|
||||
const auto old_capacity = container_capacity();
|
||||
auto result = m_data.m_value.object->emplace(lookup_key(std::forward<KeyType>(key)), nullptr);
|
||||
return set_parent(result.first->second);
|
||||
return set_parent_after_insert(result.first->second, old_capacity);
|
||||
}
|
||||
|
||||
throw_subscript_wrong_type("string");
|
||||
@@ -4524,9 +4538,9 @@ public:
|
||||
}
|
||||
|
||||
// add the element to the array (move semantics)
|
||||
const auto old_capacity = array_capacity();
|
||||
const auto old_capacity = container_capacity();
|
||||
m_data.m_value.array->push_back(std::move(val));
|
||||
set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
|
||||
set_parent_after_insert(m_data.m_value.array->back(), old_capacity);
|
||||
// if val is moved from, basic_json move constructor marks it null, so we do not call the destructor
|
||||
}
|
||||
|
||||
@@ -4555,9 +4569,9 @@ public:
|
||||
}
|
||||
|
||||
// add the element to the array
|
||||
const auto old_capacity = array_capacity();
|
||||
const auto old_capacity = container_capacity();
|
||||
m_data.m_value.array->push_back(val);
|
||||
set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
|
||||
set_parent_after_insert(m_data.m_value.array->back(), old_capacity);
|
||||
}
|
||||
|
||||
/// @brief add an object to an array
|
||||
@@ -4585,8 +4599,9 @@ public:
|
||||
}
|
||||
|
||||
// add the element to the object
|
||||
const auto old_capacity = container_capacity();
|
||||
auto res = m_data.m_value.object->insert(val);
|
||||
set_parent(res.first->second);
|
||||
set_parent_after_insert(res.first->second, old_capacity);
|
||||
}
|
||||
|
||||
/// @brief add an object to an object
|
||||
@@ -4639,9 +4654,9 @@ public:
|
||||
}
|
||||
|
||||
// add the element to the array (perfect forwarding)
|
||||
const auto old_capacity = array_capacity();
|
||||
const auto old_capacity = container_capacity();
|
||||
m_data.m_value.array->emplace_back(std::forward<Args>(args)...);
|
||||
return set_parent_after_array_insert(m_data.m_value.array->back(), old_capacity);
|
||||
return set_parent_after_insert(m_data.m_value.array->back(), old_capacity);
|
||||
}
|
||||
|
||||
/// @brief add an object to an object if key does not exist
|
||||
@@ -4662,8 +4677,9 @@ public:
|
||||
}
|
||||
|
||||
// add the element to the array (perfect forwarding)
|
||||
const auto old_capacity = container_capacity();
|
||||
auto res = m_data.m_value.object->emplace(std::forward<Args>(args)...);
|
||||
set_parent(res.first->second);
|
||||
set_parent_after_insert(res.first->second, old_capacity);
|
||||
|
||||
// create a result iterator and set iterator to the result of emplace
|
||||
auto it = begin();
|
||||
@@ -4962,9 +4978,9 @@ public:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// set_parent() also repairs the other members, which ordered_json
|
||||
// relocates when adding a key makes its vector grow
|
||||
set_parent(m_data.m_value.object->operator[](it.key()) = std::move(it.value()));
|
||||
// operator[] repairs the other members if ordered_json relocates
|
||||
// them; assigning through it keeps this O(1) per key
|
||||
operator[](it.key()) = std::move(it.value());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5014,9 +5030,8 @@ public:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// set_parent() also repairs the other members, which ordered_json
|
||||
// relocates when adding a key makes its vector grow
|
||||
target->set_parent(target->m_data.m_value.object->operator[](first.key()) = std::move(first.value()));
|
||||
// operator[] repairs the other members if ordered_json relocates them
|
||||
target->operator[](first.key()) = std::move(first.value());
|
||||
++first;
|
||||
}
|
||||
}
|
||||
@@ -6562,24 +6577,18 @@ public:
|
||||
};
|
||||
|
||||
// wrapper for "add" operation; add value at ptr
|
||||
const auto operation_add = [&result](json_pointer & ptr, const basic_json & val)
|
||||
const auto operation_add = [&result](json_pointer & ptr, basic_json val)
|
||||
{
|
||||
// adding to the root of the target document means replacing it
|
||||
if (ptr.empty())
|
||||
{
|
||||
result = val;
|
||||
result = std::move(val);
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure the top element of the pointer exists
|
||||
json_pointer const top_pointer = ptr.top();
|
||||
if (top_pointer != ptr)
|
||||
{
|
||||
result.at(top_pointer);
|
||||
}
|
||||
|
||||
// get reference to the parent of the JSON pointer ptr
|
||||
const auto last_path = ptr.back();
|
||||
// get reference to the parent of the JSON pointer ptr; a missing
|
||||
// parent (or one of its ancestors) is reported by at()
|
||||
auto last_path = std::move(ptr.reference_tokens.back());
|
||||
ptr.pop_back();
|
||||
// parent must exist when performing patch add per RFC6902 specs
|
||||
basic_json& parent = result.at(ptr);
|
||||
@@ -6590,7 +6599,7 @@ public:
|
||||
case value_t::object:
|
||||
{
|
||||
// use operator[] to add value
|
||||
parent[last_path] = val;
|
||||
parent[std::move(last_path)] = std::move(val);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -6599,7 +6608,7 @@ public:
|
||||
if (last_path == "-")
|
||||
{
|
||||
// special case: append to back
|
||||
parent.push_back(val);
|
||||
parent.push_back(std::move(val));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -6611,7 +6620,7 @@ public:
|
||||
}
|
||||
|
||||
// default case: insert add offset
|
||||
parent.insert(parent.begin() + static_cast<difference_type>(idx), val);
|
||||
parent.insert(parent.begin() + static_cast<difference_type>(idx), std::move(val));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -6634,7 +6643,8 @@ public:
|
||||
const auto operation_remove = [this, & result](json_pointer & ptr)
|
||||
{
|
||||
// get reference to the parent of the JSON pointer ptr
|
||||
const auto last_path = ptr.back();
|
||||
static_cast<void>(ptr.back()); // throws out_of_range.405 if ptr is the root
|
||||
const auto last_path = std::move(ptr.reference_tokens.back());
|
||||
ptr.pop_back();
|
||||
basic_json& parent = result.at(ptr);
|
||||
|
||||
@@ -6734,8 +6744,8 @@ public:
|
||||
}
|
||||
|
||||
// collect mandatory members
|
||||
const auto op = get_value("op", "op", true).template get<string_t>();
|
||||
const auto path = get_value(op, "path", true).template get<string_t>();
|
||||
const string_t& op = *get_value("op", "op", true).m_data.m_value.string;
|
||||
const string_t& path = *get_value(op, "path", true).m_data.m_value.string;
|
||||
json_pointer ptr(path);
|
||||
|
||||
switch (get_op(op))
|
||||
@@ -6761,7 +6771,7 @@ public:
|
||||
|
||||
case patch_operations::move:
|
||||
{
|
||||
const auto from_path = get_value("move", "from", true).template get<string_t>();
|
||||
const string_t& from_path = *get_value("move", "from", true).m_data.m_value.string;
|
||||
json_pointer from_ptr(from_path);
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
|
||||
@@ -6770,29 +6780,28 @@ public:
|
||||
}
|
||||
|
||||
// the "from" location must exist - use at()
|
||||
basic_json const v = result.at(from_ptr);
|
||||
// a root "from" is rejected by operation_remove below, which must leave result intact
|
||||
basic_json v = from_ptr.empty() ? basic_json(result) : std::move(result.at(from_ptr));
|
||||
|
||||
// The move operation is functionally identical to a
|
||||
// "remove" operation on the "from" location, followed
|
||||
// immediately by an "add" operation at the target
|
||||
// location with the value that was just removed.
|
||||
operation_remove(from_ptr);
|
||||
operation_add(ptr, v);
|
||||
operation_add(ptr, std::move(v));
|
||||
break;
|
||||
}
|
||||
|
||||
case patch_operations::copy:
|
||||
{
|
||||
const auto from_path = get_value("copy", "from", true).template get<string_t>();
|
||||
const string_t& from_path = *get_value("copy", "from", true).m_data.m_value.string;
|
||||
const json_pointer from_ptr(from_path);
|
||||
|
||||
// the "from" location must exist - use at()
|
||||
basic_json const v = result.at(from_ptr);
|
||||
|
||||
// The copy is functionally identical to an "add"
|
||||
// operation at the target location using the value
|
||||
// specified in the "from" member.
|
||||
operation_add(ptr, v);
|
||||
// specified in the "from" member; the "from" location
|
||||
// must exist - use at()
|
||||
operation_add(ptr, result.at(from_ptr));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -143,6 +143,17 @@ public:
|
||||
return {std::prev(this->end()), true};
|
||||
}
|
||||
|
||||
/// @brief add an element at the end without checking whether the key exists
|
||||
/// @note The caller must guarantee that @a key is not yet contained, otherwise
|
||||
/// the container holds duplicate keys. This avoids the linear search of
|
||||
/// emplace() and operator[] when many keys known to be new are added.
|
||||
template<class V, detail::enable_if_t<
|
||||
detail::is_constructible<T, V>::value, int> = 0>
|
||||
void emplace_new_key(const key_type& key, V && t)
|
||||
{
|
||||
append(key, std::forward<V>(t));
|
||||
}
|
||||
|
||||
T& operator[](const key_type& key)
|
||||
{
|
||||
return emplace(key, T{}).first->second;
|
||||
|
||||
Generated
+594
-193
File diff suppressed because it is too large.
Load diff
@@ -9,7 +9,10 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <numeric>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <test_data.hpp>
|
||||
|
||||
@@ -621,4 +624,27 @@ static void FromCborChunkedString(benchmark::State& state, const std::size_t chu
|
||||
|
||||
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
|
||||
|
||||
// conversion of a JSON array into standard containers
|
||||
template<typename Container>
|
||||
static void GetArray(benchmark::State& state)
|
||||
{
|
||||
json j = json::array();
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
j.push_back(i);
|
||||
}
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto c = j.get<Container>();
|
||||
benchmark::DoNotOptimize(c);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * 1000);
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(GetArray, std::vector<int>);
|
||||
BENCHMARK_TEMPLATE(GetArray, std::deque<int>);
|
||||
BENCHMARK_TEMPLATE(GetArray, std::set<int>);
|
||||
BENCHMARK_TEMPLATE(GetArray, std::array<int, 1000>);
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
|
||||
@@ -13,9 +13,11 @@
|
||||
#endif
|
||||
|
||||
#define JSON_DIAGNOSTICS 1
|
||||
#define JSON_TESTS_PRIVATE
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
using nlohmann::ordered_json;
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
@@ -631,3 +633,175 @@ TEST_CASE("Better diagnostics past the descent bound of update(), merge_patch()
|
||||
CHECK_THROWS_WITH_AS(p->at("y").at(0), expected_y.c_str(), json::type_error);
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// check that every child's parent pointer points to the containing value
|
||||
template<typename BasicJsonType>
|
||||
bool parents_consistent(const BasicJsonType& j)
|
||||
{
|
||||
bool ok = true;
|
||||
if (j.is_array() || j.is_object())
|
||||
{
|
||||
for (const auto& child : j)
|
||||
{
|
||||
ok = ok && child.m_parent == &j && parents_consistent(child);
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE_TEMPLATE("Parent pointers stay consistent", J, json, ordered_json)
|
||||
{
|
||||
SECTION("parse, copy, move, assign, swap")
|
||||
{
|
||||
J j = J::parse(R"({"a":[1,{"b":[2,3]},[4]],"c":{"d":{"e":[]}}})");
|
||||
CHECK(parents_consistent(j));
|
||||
|
||||
J copy(j);
|
||||
CHECK(parents_consistent(copy));
|
||||
|
||||
J moved(std::move(copy));
|
||||
CHECK(parents_consistent(moved));
|
||||
|
||||
J assigned;
|
||||
assigned = j;
|
||||
CHECK(parents_consistent(assigned));
|
||||
|
||||
J other = {1, 2, {{"k", J::array({1, 2})}}};
|
||||
other.swap(assigned);
|
||||
CHECK(parents_consistent(other));
|
||||
CHECK(parents_consistent(assigned));
|
||||
}
|
||||
|
||||
SECTION("push_back growth")
|
||||
{
|
||||
J arr = J::array();
|
||||
for (int i = 0; i < 500; ++i)
|
||||
{
|
||||
arr.push_back(J::array({i}));
|
||||
}
|
||||
CHECK(parents_consistent(arr));
|
||||
|
||||
J obj = J::object();
|
||||
for (int i = 0; i < 500; ++i)
|
||||
{
|
||||
obj.push_back({std::to_string(i), J::array({i})});
|
||||
}
|
||||
CHECK(parents_consistent(obj));
|
||||
}
|
||||
|
||||
SECTION("emplace_back and emplace growth")
|
||||
{
|
||||
J arr = J::array();
|
||||
J obj = J::object();
|
||||
for (int i = 0; i < 500; ++i)
|
||||
{
|
||||
arr.emplace_back(J::array({i}));
|
||||
obj.emplace(std::to_string(i), J::array({i}));
|
||||
}
|
||||
CHECK(parents_consistent(arr));
|
||||
CHECK(parents_consistent(obj));
|
||||
}
|
||||
|
||||
SECTION("operator[] growth and at()")
|
||||
{
|
||||
J obj = J::object();
|
||||
for (int i = 0; i < 500; ++i)
|
||||
{
|
||||
obj[std::to_string(i)] = J::array({i});
|
||||
CHECK(obj[std::to_string(i / 2)].m_parent == &obj);
|
||||
}
|
||||
CHECK(parents_consistent(obj));
|
||||
|
||||
J arr = J::array();
|
||||
for (int i = 0; i < 500; ++i)
|
||||
{
|
||||
arr[static_cast<size_t>(i)] = J::object({{"x", i}});
|
||||
}
|
||||
for (size_t i = 0; i < 500; ++i)
|
||||
{
|
||||
CHECK(arr.at(i).m_parent == &arr);
|
||||
}
|
||||
CHECK(obj.at("3").m_parent == &obj);
|
||||
CHECK(parents_consistent(arr));
|
||||
CHECK(parents_consistent(obj));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("update() and merge_patch() keep parent pointers and diagnostic paths", J, json, ordered_json)
|
||||
{
|
||||
// enough keys to make the vector behind ordered_json reallocate several times
|
||||
const auto make_object = [](int first, int last)
|
||||
{
|
||||
J o = J::object();
|
||||
for (int i = first; i < last; ++i)
|
||||
{
|
||||
o[std::to_string(i)] = J::object({{"x", J::array({i})}});
|
||||
}
|
||||
return o;
|
||||
};
|
||||
|
||||
// const access, so operator[] cannot repair the parent pointers
|
||||
const auto check_path = [](const J & j, const std::string & key)
|
||||
{
|
||||
const std::string expected = "[json.exception.type_error.304] (/" + key + "/x) cannot use at() with array";
|
||||
CHECK_THROWS_WITH_AS(j[key]["x"].at("y"), expected.c_str(), json::type_error);
|
||||
};
|
||||
|
||||
SECTION("update(const_reference)")
|
||||
{
|
||||
J j = make_object(0, 200);
|
||||
j.update(make_object(100, 400));
|
||||
CHECK(j.size() == 400);
|
||||
CHECK(parents_consistent(j));
|
||||
check_path(j, "0");
|
||||
check_path(j, "150");
|
||||
check_path(j, "399");
|
||||
}
|
||||
|
||||
SECTION("update(const_reference, merge_objects)")
|
||||
{
|
||||
J j = make_object(0, 200);
|
||||
j["50"]["y"] = 1;
|
||||
J other = make_object(100, 400);
|
||||
other["50"] = J::object({{"z", J::array({1})}});
|
||||
j.update(other, true);
|
||||
CHECK(j.size() == 400);
|
||||
CHECK(j["50"].size() == 3);
|
||||
CHECK(parents_consistent(j));
|
||||
check_path(j, "0");
|
||||
check_path(j, "399");
|
||||
}
|
||||
|
||||
SECTION("update(first, last)")
|
||||
{
|
||||
J j = make_object(0, 200);
|
||||
J other = make_object(100, 400);
|
||||
j.update(other.begin(), other.end());
|
||||
CHECK(j.size() == 400);
|
||||
CHECK(parents_consistent(j));
|
||||
check_path(j, "0");
|
||||
check_path(j, "399");
|
||||
|
||||
J k = make_object(0, 200);
|
||||
k.update(other.begin(), other.end(), true);
|
||||
CHECK(k.size() == 400);
|
||||
CHECK(parents_consistent(k));
|
||||
check_path(k, "399");
|
||||
}
|
||||
|
||||
SECTION("merge_patch()")
|
||||
{
|
||||
J j = make_object(0, 200);
|
||||
J patch = make_object(100, 400);
|
||||
patch["0"] = nullptr;
|
||||
j.merge_patch(patch);
|
||||
CHECK(j.size() == 399);
|
||||
CHECK_FALSE(j.contains("0"));
|
||||
CHECK(parents_consistent(j));
|
||||
check_path(j, "1");
|
||||
check_path(j, "399");
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,14 @@ using nlohmann::json;
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
TEST_CASE("JSON pointers")
|
||||
{
|
||||
@@ -1071,3 +1077,242 @@ TEST_CASE("flatten of structured values")
|
||||
R"({"/z":"s","/a~1b/~0":1,"/a~1b/k":null,"/a/0":1,"/a/1/c":null,"/a/1/b":null,"/a/2/0/x~0~1/0":true,"/a/2/0/x~0~1/1":null,"/a/2/0/w":2})");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("flatten of ordered_json")
|
||||
{
|
||||
using nlohmann::ordered_json;
|
||||
|
||||
SECTION("keeps the document order of the leaves")
|
||||
{
|
||||
const ordered_json j = ordered_json::parse(R"({
|
||||
"z": 1, "a": {"y": [true, null, {}], "b": "x", "": []},
|
||||
"m~n": 2.5, "o/p": {"k": {}}, "0": [[1], [2, 3]]
|
||||
})");
|
||||
|
||||
const ordered_json flat = j.flatten();
|
||||
std::vector<std::string> keys;
|
||||
for (const auto& el : flat.items())
|
||||
{
|
||||
keys.push_back(el.key());
|
||||
}
|
||||
const std::vector<std::string> expected =
|
||||
{
|
||||
"/z", "/a/y/0", "/a/y/1", "/a/y/2", "/a/b", "/a/", "/m~0n", "/o~1p/k", "/0/0/0", "/0/1/0", "/0/1/1"
|
||||
};
|
||||
CHECK(keys == expected);
|
||||
CHECK(flat.size() == expected.size());
|
||||
CHECK(flat["/m~0n"] == 2.5);
|
||||
CHECK(flat["/a/y/2"].is_null());
|
||||
}
|
||||
|
||||
SECTION("round-trips and agrees with json")
|
||||
{
|
||||
ordered_json j;
|
||||
for (int i = 0; i < 200; ++i)
|
||||
{
|
||||
j["k" + std::to_string(i)] = {{"v", i}, {"l", {i, i + 1, "s"}}};
|
||||
}
|
||||
const ordered_json flat = j.flatten();
|
||||
CHECK(flat.size() == 800);
|
||||
CHECK(flat.unflatten() == j);
|
||||
|
||||
// same entries as for a sorted object
|
||||
const nlohmann::json plain = nlohmann::json::parse(j.dump());
|
||||
CHECK(nlohmann::json::parse(flat.dump()) == plain.flatten());
|
||||
}
|
||||
|
||||
SECTION("primitive and empty root")
|
||||
{
|
||||
CHECK(ordered_json(1).flatten() == ordered_json({{"", 1}}));
|
||||
CHECK(ordered_json::array().flatten() == ordered_json({{"", nullptr}}));
|
||||
}
|
||||
}
|
||||
namespace
|
||||
{
|
||||
// reference implementation of the array index rules (RFC 6901, Sect. 4) based on strtoull
|
||||
enum class index_class
|
||||
{
|
||||
ok,
|
||||
leading_zero,
|
||||
not_a_number,
|
||||
unresolved,
|
||||
exceeds_size_type
|
||||
};
|
||||
|
||||
index_class classify_index(const std::string& s, std::size_t& idx)
|
||||
{
|
||||
if (s.size() > 1 && s[0] == '0')
|
||||
{
|
||||
return index_class::leading_zero;
|
||||
}
|
||||
if (!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9'))
|
||||
{
|
||||
return index_class::not_a_number;
|
||||
}
|
||||
char* end = nullptr;
|
||||
errno = 0;
|
||||
const unsigned long long res = std::strtoull(s.c_str(), &end, 10); // NOLINT(runtime/int)
|
||||
if (end == s.c_str() || errno == ERANGE || static_cast<std::size_t>(end - s.c_str()) != s.size())
|
||||
{
|
||||
return index_class::unresolved;
|
||||
}
|
||||
if (res >= static_cast<unsigned long long>((std::numeric_limits<std::size_t>::max)())) // NOLINT(runtime/int)
|
||||
{
|
||||
return index_class::exceeds_size_type;
|
||||
}
|
||||
idx = static_cast<std::size_t>(res);
|
||||
return index_class::ok;
|
||||
}
|
||||
|
||||
// the observable behavior of json_pointer::at() and contains() for an array index token
|
||||
void check_index_token(const std::string& token)
|
||||
{
|
||||
const json array = {10, 20, 30};
|
||||
const json::json_pointer ptr("/" + token);
|
||||
|
||||
std::size_t idx = 0;
|
||||
const auto cls = classify_index(token, idx);
|
||||
const bool exists = cls == index_class::ok && idx < array.size();
|
||||
|
||||
CAPTURE(token)
|
||||
CHECK(array.contains(ptr) == (exists && token != "-"));
|
||||
|
||||
if (token == "-")
|
||||
{
|
||||
CHECK_THROWS_AS(array.at(ptr), json::out_of_range&);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cls)
|
||||
{
|
||||
case index_class::leading_zero:
|
||||
CHECK_THROWS_AS(array.at(ptr), json::parse_error&);
|
||||
break;
|
||||
case index_class::not_a_number:
|
||||
CHECK_THROWS_AS(array.at(ptr), json::parse_error&);
|
||||
break;
|
||||
case index_class::unresolved:
|
||||
case index_class::exceeds_size_type:
|
||||
CHECK_THROWS_AS(array.at(ptr), json::out_of_range&);
|
||||
break;
|
||||
case index_class::ok:
|
||||
default:
|
||||
if (exists)
|
||||
{
|
||||
CHECK(array.at(ptr) == array[idx]);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_THROWS_AS(array.at(ptr), json::out_of_range&);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("JSON pointer array index tokens match strtoull semantics")
|
||||
{
|
||||
SECTION("boundary values")
|
||||
{
|
||||
const std::string max_digits = std::to_string((std::numeric_limits<std::size_t>::max)());
|
||||
const std::vector<std::string> tokens =
|
||||
{
|
||||
"", "-", "0", "1", "2", "3", "00", "01", "007", "+1", "-1", " 1", "1 ", "1a", "a1", "0x1", "1e1", "1.0",
|
||||
"999999999", "1000000000", "99999999999999999", "999999999999999999", "1000000000000000000",
|
||||
"9999999999999999999", "18446744073709551614", max_digits, "18446744073709551616",
|
||||
"99999999999999999999999", "000000000000000000000"
|
||||
};
|
||||
for (const auto& token : tokens)
|
||||
{
|
||||
check_index_token(token);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("random tokens")
|
||||
{
|
||||
std::mt19937 gen(42);
|
||||
const std::string alphabet = "0123456789012345678901234567890123456789-+ ex";
|
||||
for (int i = 0; i < 5000; ++i)
|
||||
{
|
||||
std::string token(static_cast<std::size_t>(gen() % 23), '0');
|
||||
for (auto& c : token)
|
||||
{
|
||||
c = alphabet[gen() % alphabet.size()];
|
||||
}
|
||||
check_index_token(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON pointer operations on pointers with escaped tokens")
|
||||
{
|
||||
const std::vector<std::string> strings =
|
||||
{
|
||||
"", "/", "//", "/a", "/a/b", "/a~1b", "/~0", "/~01", "/~10", "/a~0~1/b/", "/0/1/2", "/ ", "/a/", "/~1/~0"
|
||||
};
|
||||
|
||||
for (const auto& str : strings)
|
||||
{
|
||||
CAPTURE(str)
|
||||
const json::json_pointer ptr(str);
|
||||
CHECK(ptr.to_string() == str);
|
||||
CHECK(ptr / "x" / "y" / std::size_t{3} == json::json_pointer(str + "/x/y/3"));
|
||||
CHECK(ptr.parent_pointer() == (str.empty() ? ptr : json::json_pointer(str.substr(0, str.rfind('/')))));
|
||||
|
||||
// comparison with a string, in place, and with every other pointer
|
||||
#if !defined(JSON_TEST_DEPRECATED_FUNCTIONS_DELETED)
|
||||
for (const auto& other : strings)
|
||||
{
|
||||
CAPTURE(other)
|
||||
CHECK((ptr == other) == (str == other));
|
||||
CHECK((other == ptr) == (str == other));
|
||||
CHECK((ptr != other) == (str != other));
|
||||
}
|
||||
CHECK_THROWS_AS(ptr == "x", json::parse_error&);
|
||||
CHECK_THROWS_AS(ptr == "/~", json::parse_error&);
|
||||
CHECK_THROWS_AS(ptr == "/b/~2", json::parse_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON pointer lookups")
|
||||
{
|
||||
json j = {{"a", {{"b", {1, 2, 3}}}}, {"", 7}, {"~/", {{"x", nullptr}}}};
|
||||
const json& cj = j;
|
||||
|
||||
CHECK(cj[json::json_pointer("/a/b/2")] == 3);
|
||||
CHECK(cj.at(json::json_pointer("/a/b/1")) == 2);
|
||||
CHECK(cj.at(json::json_pointer("/")) == 7);
|
||||
CHECK(cj.contains(json::json_pointer("/~0~1/x")));
|
||||
CHECK(!cj.contains(json::json_pointer("/a/c")));
|
||||
CHECK(cj.value(json::json_pointer("/a/b/0"), 5) == 1);
|
||||
CHECK(cj.value(json::json_pointer("/a/b/3"), 5) == 5);
|
||||
CHECK(cj.value(json::json_pointer("/a/zz"), 5) == 5);
|
||||
|
||||
CHECK_THROWS_AS(cj.at(json::json_pointer("/a/c")), json::out_of_range&);
|
||||
CHECK_THROWS_AS(cj.at(json::json_pointer("/a/b/3")), json::out_of_range&);
|
||||
|
||||
// non-const access reuses existing entries and creates missing ones
|
||||
j[json::json_pointer("/a/b/1")] = 20;
|
||||
j[json::json_pointer("/n/m")] = 1;
|
||||
CHECK(j["a"]["b"] == json({1, 20, 3}));
|
||||
CHECK(j["n"]["m"] == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("JSON patch with moves and copies")
|
||||
{
|
||||
json doc = {{"a", {{"b", {1, 2, 3}}}}, {"c", {{"d", "x"}}}};
|
||||
|
||||
CHECK(doc.patch(json::parse(R"([{"op":"move","from":"/a/b","path":"/c/e"}])")) ==
|
||||
json::parse(R"({"a":{},"c":{"d":"x","e":[1,2,3]}})"));
|
||||
CHECK(doc.patch(json::parse(R"([{"op":"copy","from":"/a/b","path":"/c/e"}])")) ==
|
||||
json::parse(R"({"a":{"b":[1,2,3]},"c":{"d":"x","e":[1,2,3]}})"));
|
||||
CHECK(doc.patch(json::parse(R"([{"op":"move","from":"/a/b/0","path":"/a/b/2"}])")) ==
|
||||
json::parse(R"({"a":{"b":[2,3,1]},"c":{"d":"x"}})"));
|
||||
|
||||
// a failing move leaves a copy-patched document untouched
|
||||
CHECK_THROWS_AS(doc.patch(json::parse(R"([{"op":"move","from":"","path":""}])")), json::out_of_range&);
|
||||
json inplace = doc;
|
||||
CHECK_THROWS_AS(inplace.patch_inplace(json::parse(R"([{"op":"move","from":"","path":""}])")), json::out_of_range&);
|
||||
CHECK(inplace == doc);
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <forward_list>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <valarray>
|
||||
#include <vector>
|
||||
|
||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1)
|
||||
#if __has_include(<filesystem>)
|
||||
#include <filesystem>
|
||||
#define TO_JSON_RVALUE_HAS_FS 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// element type that counts how it is converted to JSON: a conversion from an
|
||||
// lvalue copies, a conversion from an rvalue moves
|
||||
struct tracked
|
||||
{
|
||||
std::string payload;
|
||||
bool moved_from = false;
|
||||
|
||||
explicit tracked(std::string p) : payload(std::move(p)) {}
|
||||
tracked(const tracked&) = default;
|
||||
tracked& operator=(const tracked&) = default;
|
||||
tracked(tracked&& other) noexcept : payload(std::move(other.payload)), moved_from(other.moved_from)
|
||||
{
|
||||
other.moved_from = true;
|
||||
}
|
||||
tracked& operator=(tracked&& other) noexcept
|
||||
{
|
||||
payload = std::move(other.payload);
|
||||
moved_from = other.moved_from;
|
||||
other.moved_from = true;
|
||||
return *this;
|
||||
}
|
||||
~tracked() = default;
|
||||
};
|
||||
|
||||
int tracked_copies = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables,misc-use-internal-linkage)
|
||||
int tracked_moves = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables,misc-use-internal-linkage)
|
||||
|
||||
void to_json(json& j, const tracked& t)
|
||||
{
|
||||
++tracked_copies;
|
||||
j = t.payload;
|
||||
}
|
||||
|
||||
void to_json(json& j, tracked&& t)
|
||||
{
|
||||
++tracked_moves;
|
||||
j = std::move(t.payload);
|
||||
t.moved_from = true;
|
||||
}
|
||||
|
||||
// user type with its own rvalue-aware serializer
|
||||
struct udt_container
|
||||
{
|
||||
std::vector<int> values;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace nlohmann
|
||||
{
|
||||
template<>
|
||||
struct adl_serializer<udt_container>
|
||||
{
|
||||
static void to_json(json& j, const udt_container& c)
|
||||
{
|
||||
j = c.values;
|
||||
j.push_back("copied");
|
||||
}
|
||||
static void to_json(json& j, udt_container&& c)
|
||||
{
|
||||
j = std::move(c.values);
|
||||
j.push_back("moved");
|
||||
}
|
||||
};
|
||||
} // namespace nlohmann
|
||||
|
||||
TEST_CASE("to_json from rvalue containers")
|
||||
{
|
||||
const std::string long_a(64, 'a');
|
||||
const std::string long_b(64, 'b');
|
||||
|
||||
SECTION("vector<std::string>: elements are moved")
|
||||
{
|
||||
std::vector<std::string> v{long_a, long_b};
|
||||
const json j = std::move(v);
|
||||
CHECK(j == json::array({long_a, long_b}));
|
||||
for (const auto& s : v) // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
|
||||
{
|
||||
CHECK(s.empty()); // long strings leave the source element empty when moved
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("vector<std::string>: lvalues are copied and left untouched")
|
||||
{
|
||||
const std::vector<std::string> v{long_a, long_b};
|
||||
const json j = v;
|
||||
CHECK(j == json::array({long_a, long_b}));
|
||||
CHECK(v[0] == long_a);
|
||||
CHECK(v[1] == long_b);
|
||||
|
||||
std::vector<std::string> v2{long_a, long_b};
|
||||
const json j2 = v2;
|
||||
CHECK(j2 == j);
|
||||
CHECK(v2[0] == long_a);
|
||||
CHECK(v2[1] == long_b);
|
||||
}
|
||||
|
||||
SECTION("tracked elements: rvalue moves, lvalue copies")
|
||||
{
|
||||
tracked_copies = 0;
|
||||
tracked_moves = 0;
|
||||
|
||||
std::vector<tracked> v;
|
||||
v.emplace_back("x");
|
||||
v.emplace_back("y");
|
||||
|
||||
const json copy = v;
|
||||
CHECK(copy == json::array({"x", "y"}));
|
||||
CHECK(tracked_copies == 2);
|
||||
CHECK(tracked_moves == 0);
|
||||
CHECK_FALSE(v[0].moved_from);
|
||||
|
||||
const json moved = std::move(v);
|
||||
CHECK(moved == copy);
|
||||
CHECK(tracked_copies == 2);
|
||||
CHECK(tracked_moves == 2);
|
||||
}
|
||||
|
||||
SECTION("const rvalue is copied")
|
||||
{
|
||||
tracked_copies = 0;
|
||||
tracked_moves = 0;
|
||||
const std::vector<tracked> v(2, tracked("z"));
|
||||
const json j = std::move(v); // NOLINT(performance-move-const-arg,hicpp-move-const-arg)
|
||||
CHECK(j == json::array({"z", "z"}));
|
||||
CHECK(tracked_copies == 2);
|
||||
CHECK(tracked_moves == 0);
|
||||
}
|
||||
|
||||
SECTION("other sequence containers")
|
||||
{
|
||||
CHECK(json(std::deque<std::string> {long_a, long_b}) == json::array({long_a, long_b}));
|
||||
CHECK(json(std::list<std::string> {long_a, long_b}) == json::array({long_a, long_b}));
|
||||
CHECK(json(std::forward_list<std::string> {long_a, long_b}) == json::array({long_a, long_b}));
|
||||
CHECK(json(std::array<std::string, 2> {{long_a, long_b}}) == json::array({long_a, long_b}));
|
||||
CHECK(json(std::set<std::string> {long_a, long_b}) == json::array({long_a, long_b}));
|
||||
CHECK(json(std::vector<bool> {true, false}) == json::array({true, false}));
|
||||
CHECK(json(std::vector<int> {1, 2, 3}) == json::array({1, 2, 3}));
|
||||
CHECK(json(std::vector<std::vector<std::string>> {{long_a}, {long_b}}) == json::array({json::array({long_a}), json::array({long_b})}));
|
||||
CHECK(json(std::vector<json> {1, "a"}) == json::array({1, "a"}));
|
||||
}
|
||||
|
||||
SECTION("valarray")
|
||||
{
|
||||
std::valarray<int> va(3);
|
||||
va[0] = 1;
|
||||
va[1] = 2;
|
||||
va[2] = 3;
|
||||
CHECK(json(std::move(va)) == json::array({1, 2, 3})); // NOLINT(performance-move-const-arg,hicpp-move-const-arg)
|
||||
CHECK(json(std::valarray<int>(2)) == json::array({0, 0}));
|
||||
}
|
||||
|
||||
SECTION("array_t and json arrays")
|
||||
{
|
||||
json::array_t arr{1, 2};
|
||||
CHECK(json(std::move(arr)) == json::array({1, 2}));
|
||||
json a = json::array({1, 2});
|
||||
const json b = std::move(a);
|
||||
CHECK(b == json::array({1, 2}));
|
||||
}
|
||||
|
||||
SECTION("object-like containers")
|
||||
{
|
||||
std::map<std::string, std::string> m{{"a", long_a}, {"b", long_b}};
|
||||
const json j = std::move(m);
|
||||
CHECK(j == json({{"a", long_a}, {"b", long_b}}));
|
||||
for (const auto& kv : m) // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
|
||||
{
|
||||
CHECK(kv.second.empty()); // values moved, keys (const) copied
|
||||
}
|
||||
|
||||
const std::map<std::string, std::string> cm{{"a", long_a}};
|
||||
const json jc = cm;
|
||||
CHECK(jc["a"] == long_a);
|
||||
CHECK(cm.at("a") == long_a);
|
||||
|
||||
std::unordered_map<std::string, std::string> um{{"a", long_a}};
|
||||
CHECK(json(std::move(um)) == json({{"a", long_a}}));
|
||||
|
||||
std::multimap<std::string, int> mm{{"a", 1}};
|
||||
CHECK(json(std::move(mm)) == json({{"a", 1}}));
|
||||
|
||||
tracked_copies = 0;
|
||||
tracked_moves = 0;
|
||||
std::map<std::string, tracked> mt;
|
||||
mt.emplace("k", tracked("v"));
|
||||
const json jt = std::move(mt);
|
||||
CHECK(jt == json({{"k", "v"}}));
|
||||
CHECK(tracked_moves == 1);
|
||||
CHECK(tracked_copies == 0);
|
||||
|
||||
json::object_t obj{{"a", 1}};
|
||||
CHECK(json(std::move(obj)) == json({{"a", 1}}));
|
||||
}
|
||||
|
||||
SECTION("user type with its own to_json overloads")
|
||||
{
|
||||
udt_container c{{1, 2}};
|
||||
const json lv = c;
|
||||
CHECK(lv == json::array({1, 2, "copied"}));
|
||||
const json rv = std::move(c);
|
||||
CHECK(rv == json::array({1, 2, "moved"}));
|
||||
}
|
||||
|
||||
SECTION("assignment and implicit conversion")
|
||||
{
|
||||
std::vector<std::string> v{long_a};
|
||||
json j;
|
||||
j = std::move(v);
|
||||
CHECK(j == json::array({long_a}));
|
||||
json k = {std::vector<std::string>{long_b}};
|
||||
CHECK(k.size() == 1);
|
||||
}
|
||||
|
||||
#ifdef TO_JSON_RVALUE_HAS_FS
|
||||
SECTION("std::filesystem::path rvalue is still a string")
|
||||
{
|
||||
CHECK(json(std::filesystem::path("a/b")) == json("a/b"));
|
||||
#ifdef __cpp_lib_char8_t
|
||||
CHECK(json(std::u8string(u8"a/b")) == json("a/b"));
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Reference in new issue
Block a user