mirror of
https://github.com/nlohmann/json.git
synced 2026-10-15 02:37:13 +00:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77ebf7f9e1 | ||
|
|
2cac35c6ad | ||
|
|
d5e51d03c0 | ||
|
|
ffa987ac40 | ||
|
|
735a20e36d | ||
|
|
45b837885e |
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 type for
|
||||
With the default values for `ArrayType` (`std::vector`) and `AllocatorType` (`std::allocator`), the default value for
|
||||
`array_t` is:
|
||||
|
||||
```cpp
|
||||
|
||||
@@ -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 type for `boolean_t` is `#!cpp bool`.
|
||||
With the default values for `BooleanType` (`#!cpp bool`), the default value for `boolean_t` is `#!cpp bool`.
|
||||
|
||||
### Storage
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ constexpr bool is_number_unsigned() const noexcept;
|
||||
|
||||
This function returns `#!cpp true` if and only if the JSON value is an unsigned integer number. This excludes
|
||||
floating-point and signed integer values.
|
||||
|
||||
If [`number_unsigned_t`](number_unsigned_t.md) is the same type as [`number_integer_t`](number_integer_t.md), every
|
||||
integer is stored as `number_integer_t`, and this function always returns `#!cpp false`.
|
||||
|
||||
## Return value
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ type to use.
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for `NumberFloatType` (`double`), the default type for `number_float_t` is `#!cpp double`.
|
||||
With the default values for `NumberFloatType` (`double`), the default value for `number_float_t` is `#!cpp double`.
|
||||
|
||||
### Default behavior
|
||||
|
||||
@@ -52,9 +52,8 @@ With the default values for `NumberFloatType` (`double`), the default type 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 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
|
||||
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
|
||||
[`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 type for `number_integer_t` is
|
||||
With the default values for `NumberIntegerType` (`std::int64_t`), the default value for `number_integer_t` is
|
||||
`#!cpp std::int64_t`.
|
||||
|
||||
### Default behavior
|
||||
|
||||
@@ -25,14 +25,15 @@ the type to use.
|
||||
`NumberUnsignedType`
|
||||
: the type to store unsigned integers. It must be an **unsigned integral** type (`#!cpp std::is_integral`) with a
|
||||
`#!cpp std::numeric_limits` specialization, and it must be able to represent the absolute value of every
|
||||
[`number_integer_t`](number_integer_t.md) value. See
|
||||
[`number_integer_t`](number_integer_t.md) value. Alternatively, it can be the same type as
|
||||
[`number_integer_t`](number_integer_t.md); then every integer is stored as `number_integer_t`. See
|
||||
[Template Parameter Requirements](../../features/types/template_parameters.md#numberintegertype-and-numberunsignedtype).
|
||||
|
||||
## Notes
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for `NumberUnsignedType` (`std::uint64_t`), the default type for `number_unsigned_t` is
|
||||
With the default values for `NumberUnsignedType` (`std::uint64_t`), the default value for `number_unsigned_t` is
|
||||
`#!cpp std::uint64_t`.
|
||||
|
||||
### Default behavior
|
||||
@@ -89,3 +90,4 @@ Integer number values are stored directly inside a `basic_json` type.
|
||||
## Version history
|
||||
|
||||
- Added in version 2.0.0.
|
||||
- Can be the same type as `number_integer_t` since version 3.13.0.
|
||||
@@ -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 type for `object_t` is:
|
||||
(`std::allocator`), the default value 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 type for `string_t` is `#!cpp std::string`.
|
||||
With the default values for `StringType` (`std::string`), the default value 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 type for `object_t` is:
|
||||
With the default values for *ObjectType* (`std::map`), *StringType* (`std::string`), and *AllocatorType* (`std::allocator`), the default value 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 type for `array_t` is:
|
||||
With the default values for *ArrayType* (`std::vector`) and *AllocatorType* (`std::allocator`), the default value 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 type for `string_t` is `#!cpp std::string`.
|
||||
With the default values for *StringType* (`std::string`), the default value 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 type for `boolean_t` is `#!cpp bool`.
|
||||
With the default values for *BooleanType* (`#!cpp bool`), the default value 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 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`.
|
||||
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`.
|
||||
|
||||
### 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 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`.
|
||||
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`.
|
||||
|
||||
### Storage
|
||||
|
||||
|
||||
@@ -345,8 +345,11 @@ The number types can be changed with template parameters.
|
||||
in case of overflow.
|
||||
- The type for unsigned integers must be convertible from `#!c unsigned long long`. The type for floating-point
|
||||
numbers is used in case of overflow.
|
||||
- The types for signed and unsigned integers must be distinct, see
|
||||
[#2573](https://github.com/nlohmann/json/issues/2573).
|
||||
- The type for unsigned integers can also be the same signed type as the type for signed integers (e.g.,
|
||||
`#!c std::int64_t` for both). Then every integer is stored as a signed integer, and
|
||||
[`is_number_unsigned()`](../../api/basic_json/is_number_unsigned.md) is always `#!c false`, see
|
||||
[Template Parameter Requirements](template_parameters.md#same-type-for-both).
|
||||
- The type for floating-point numbers must be distinct from the integer types.
|
||||
- Only `#!c double`, `#!c float`, and `#!c long double` are supported for floating-point numbers.
|
||||
|
||||
!!! example
|
||||
|
||||
@@ -40,19 +40,19 @@ Requirements are split into two groups:
|
||||
|
||||
## Overview
|
||||
|
||||
| Template parameter | Default | Notable substitutes |
|
||||
|-------------------------------------------------------------------|-----------------------------------|-----------------------------------------------------------------------|
|
||||
| [`ObjectType`](#objecttype) | `std::map` | [`nlohmann::ordered_map`](../../api/ordered_map.md), Abseil hash maps |
|
||||
| [`ArrayType`](#arraytype) | `std::vector` | `#!cpp std::deque` |
|
||||
| [`StringType`](#stringtype) | `std::string` | `std::string`-like types over `char` |
|
||||
| [`BooleanType`](#booleantype) | `bool` | none worth using |
|
||||
| [`NumberIntegerType`](#numberintegertype-and-numberunsignedtype) | `std::int64_t` | any signed integer type |
|
||||
| [`NumberUnsignedType`](#numberintegertype-and-numberunsignedtype) | `std::uint64_t` | any unsigned integer type at least as wide as `NumberIntegerType` |
|
||||
| [`NumberFloatType`](#numberfloattype) | `double` | `float` (`long double`: no binary formats) |
|
||||
| [`AllocatorType`](#allocatortype) | `std::allocator` | stateless allocators |
|
||||
| [`JSONSerializer`](#jsonserializer) | `adl_serializer` | serializers with the same interface |
|
||||
| [`BinaryType`](#binarytype) | `#!cpp std::vector<std::uint8_t>` | `#!cpp std::vector<char>` |
|
||||
| [`CustomBaseClass`](#custombaseclass) | `void` | any default-constructible class |
|
||||
| Template parameter | Default | Notable substitutes |
|
||||
|-------------------------------------------------------------------|-----------------------------------|--------------------------------------------------------------------------------------------------|
|
||||
| [`ObjectType`](#objecttype) | `std::map` | [`nlohmann::ordered_map`](../../api/ordered_map.md), Abseil hash maps |
|
||||
| [`ArrayType`](#arraytype) | `std::vector` | `#!cpp std::deque` |
|
||||
| [`StringType`](#stringtype) | `std::string` | `std::string`-like types over `char` |
|
||||
| [`BooleanType`](#booleantype) | `bool` | none worth using |
|
||||
| [`NumberIntegerType`](#numberintegertype-and-numberunsignedtype) | `std::int64_t` | any signed integer type |
|
||||
| [`NumberUnsignedType`](#numberintegertype-and-numberunsignedtype) | `std::uint64_t` | any unsigned integer type at least as wide as `NumberIntegerType`, or `NumberIntegerType` itself |
|
||||
| [`NumberFloatType`](#numberfloattype) | `double` | `float` (`long double`: no binary formats) |
|
||||
| [`AllocatorType`](#allocatortype) | `std::allocator` | stateless allocators |
|
||||
| [`JSONSerializer`](#jsonserializer) | `adl_serializer` | serializers with the same interface |
|
||||
| [`BinaryType`](#binarytype) | `#!cpp std::vector<std::uint8_t>` | `#!cpp std::vector<char>` |
|
||||
| [`CustomBaseClass`](#custombaseclass) | `void` | any default-constructible class |
|
||||
|
||||
!!! warning "Third-party containers and incomplete types"
|
||||
|
||||
@@ -504,13 +504,14 @@ Both types are stored **directly** inside `basic_json`'s union.
|
||||
### Always required
|
||||
|
||||
- `#!cpp std::is_integral` must be satisfied: `NumberIntegerType` must be a **signed** integer type,
|
||||
`NumberUnsignedType` an **unsigned** integer type. Class types are not supported -- among others, the constructors
|
||||
taking integer values are constrained on `#!cpp std::is_integral`.
|
||||
`NumberUnsignedType` an **unsigned** integer type or the same type as `NumberIntegerType` (see
|
||||
[below](#same-type-for-both)). Class types are not supported -- among others, the constructors taking integer values
|
||||
are constrained on `#!cpp std::is_integral`.
|
||||
- Trivially default-constructible, trivially copyable, and trivially destructible (union member).
|
||||
- `#!cpp std::numeric_limits` must be specialized for both types.
|
||||
- `NumberUnsignedType` must be able to represent the absolute value of every `NumberIntegerType` value; serialization
|
||||
of negative numbers converts the value to `NumberUnsignedType`. A `#!cpp static_assert` requires it to be at least as
|
||||
wide as `NumberIntegerType`, which is what that amounts to for the standard integer types.
|
||||
- An unsigned `NumberUnsignedType` must be able to represent the absolute value of every `NumberIntegerType` value;
|
||||
serialization of negative numbers converts the value to `NumberUnsignedType`. A `#!cpp static_assert` requires it to
|
||||
be at least as wide as `NumberIntegerType`, which is what that amounts to for the standard integer types.
|
||||
- Both types must fit into the internal 64-character number buffer used by
|
||||
[`dump`](../../api/basic_json/dump.md), which is the case for all standard integer types.
|
||||
- [`std::hash<basic_json>`](../../api/basic_json/std_hash.md) additionally requires `#!cpp std::hash` specializations.
|
||||
@@ -522,6 +523,23 @@ is stored as [`number_float_t`](../../api/basic_json/number_float_t.md) instead.
|
||||
therefore silently changes parse results rather than raising an error. See
|
||||
[Number Handling](number_handling.md) for details.
|
||||
|
||||
#### Same type for both
|
||||
|
||||
`NumberUnsignedType` may be the same type as `NumberIntegerType`, e.g.
|
||||
`#!cpp nlohmann::json::with_integers_t<std::int64_t, std::int64_t>` (see
|
||||
[`with_integers_t`](../../api/basic_json/with_t.md)), for code that only wants to handle one integer type. Such a `basic_json` stores every integer as
|
||||
[`number_integer_t`](../../api/basic_json/number_integer_t.md):
|
||||
|
||||
- [`is_number_unsigned()`](../../api/basic_json/is_number_unsigned.md) is always `#!cpp false`, and
|
||||
[`type()`](../../api/basic_json/type.md) never returns `value_t::number_unsigned`. Constructing a value from
|
||||
`value_t::number_unsigned` gives a `value_t::number_integer` value.
|
||||
- Values of unsigned C++ types (e.g., `#!cpp 5u`) are converted to `NumberIntegerType`, so values above its maximum
|
||||
wrap around, as values outside the range of a narrower `NumberIntegerType` already do.
|
||||
- The parsers store a non-negative integer above the maximum of `NumberIntegerType` (e.g., `18446744073709551615` for
|
||||
`#!cpp std::int64_t`) as [`number_float_t`](../../api/basic_json/number_float_t.md), like any other integer that does
|
||||
not fit.
|
||||
- Converting from a `basic_json` with distinct types turns its unsigned values into `number_integer_t` values.
|
||||
|
||||
### Compatible types
|
||||
|
||||
| Type pair | Support |
|
||||
@@ -529,8 +547,9 @@ therefore silently changes parse results rather than raising an error. See
|
||||
| `#!cpp std::int64_t` / `#!cpp std::uint64_t` (default) | full |
|
||||
| `#!cpp std::int32_t` / `#!cpp std::uint32_t`, `#!cpp long long` / `#!cpp unsigned long long` | full; narrower types change which literals the parser can represent |
|
||||
| any other pair of standard signed/unsigned integer types | full |
|
||||
| the same signed integer type for both, e.g. `#!cpp std::int64_t` / `#!cpp std::int64_t` | full; every integer is stored as `number_integer_t`, see [above](#same-type-for-both) |
|
||||
| class types, enumerations | not usable; `#!cpp std::is_integral` must hold |
|
||||
| `#!cpp bool`, or a type already used for another member of the union | not usable; `#!cpp std::is_integral<bool>` is in fact `#!cpp true`, but the `get_impl_ptr` overloads for `boolean_t`, `number_integer_t`, `number_unsigned_t` and `number_float_t` would collide |
|
||||
| `#!cpp bool`, `number_float_t`, or the type of another member of the union | not usable; `#!cpp std::is_integral<bool>` is in fact `#!cpp true`, but the `get_impl_ptr` overloads for `boolean_t`, `number_integer_t`, `number_unsigned_t` and `number_float_t` would collide |
|
||||
|
||||
## `NumberFloatType`
|
||||
|
||||
|
||||
@@ -80,7 +80,11 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
|
||||
{
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
|
||||
// never null: a basic_json whose number_unsigned_t is number_integer_t
|
||||
// stores no number_unsigned values (#2573)
|
||||
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
|
||||
JSON_ASSERT(ptr != nullptr);
|
||||
val = static_cast<ArithmeticType>(*ptr);
|
||||
break;
|
||||
}
|
||||
case value_t::number_integer:
|
||||
@@ -150,7 +154,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_flo
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val)
|
||||
inline void from_json(const BasicJsonType& j, number_unsigned_overload_t<BasicJsonType>& val)
|
||||
{
|
||||
get_arithmetic_value(j, val);
|
||||
}
|
||||
@@ -406,7 +410,11 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val)
|
||||
{
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
|
||||
// never null: a basic_json whose number_unsigned_t is number_integer_t
|
||||
// stores no number_unsigned values (#2573)
|
||||
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
|
||||
JSON_ASSERT(ptr != nullptr);
|
||||
val = static_cast<ArithmeticType>(*ptr);
|
||||
break;
|
||||
}
|
||||
case value_t::number_integer:
|
||||
|
||||
@@ -303,14 +303,14 @@ inline void to_json(BasicJsonType& j, FloatType val) noexcept
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename CompatibleNumberUnsignedType,
|
||||
enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0>
|
||||
enable_if_t<is_compatible_integer_type<number_unsigned_overload_t<BasicJsonType>, CompatibleNumberUnsignedType>::value, int> = 0>
|
||||
inline void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept
|
||||
{
|
||||
external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val));
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename CompatibleNumberIntegerType,
|
||||
enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0>
|
||||
enable_if_t<is_compatible_number_integer_type<BasicJsonType, CompatibleNumberIntegerType>::value, int> = 0>
|
||||
inline void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept
|
||||
{
|
||||
external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val));
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include <cstring> // memcpy
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <type_traits> // conditional, integral_constant, true_type, false_type
|
||||
#include <type_traits> // conditional, integral_constant, is_signed, true_type, false_type
|
||||
#include <utility> // move
|
||||
|
||||
#include <nlohmann/detail/bit_ops.hpp>
|
||||
@@ -50,6 +50,20 @@ NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
|
||||
// whether an integer of a signed type is negative; the overload for other types
|
||||
// avoids comparing an unsigned value with zero
|
||||
template<typename NumberType>
|
||||
constexpr bool is_negative_integer(NumberType x, std::true_type /*is_signed*/) noexcept
|
||||
{
|
||||
return x < 0;
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
constexpr bool is_negative_integer(NumberType /*unused*/, std::false_type /*is_signed*/) noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief fast integer parser for an already-validated unsigned integer
|
||||
|
||||
@@ -83,8 +97,10 @@ bool parse_integer_unsigned(const char* first, const char* last, NumberUnsignedT
|
||||
x = (x * 10u) + digit;
|
||||
}
|
||||
value = static_cast<NumberUnsignedType>(x);
|
||||
// reject values that do not round-trip into a narrower NumberUnsignedType
|
||||
return static_cast<std::uint64_t>(value) == x;
|
||||
// reject values that do not round-trip into a narrower NumberUnsignedType,
|
||||
// and values that wrap around to negative numbers if it is a signed type
|
||||
// (number_unsigned_t may be the same type as number_integer_t, #2573)
|
||||
return static_cast<std::uint64_t>(value) == x && !is_negative_integer(value, std::is_signed<NumberUnsignedType>());
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#endif // JSON_NO_IO
|
||||
#include <limits> // max
|
||||
#include <map> // map
|
||||
#include <numeric> // accumulate
|
||||
#include <string> // string
|
||||
#include <utility> // move
|
||||
#include <vector> // vector
|
||||
@@ -68,21 +69,12 @@ class json_pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
|
||||
string_t to_string() const
|
||||
{
|
||||
// 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 std::accumulate(reference_tokens.begin(), reference_tokens.end(),
|
||||
string_t{},
|
||||
[](const string_t& a, const string_t& 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;
|
||||
return detail::concat<string_t>(a, '/', detail::escape(b));
|
||||
});
|
||||
}
|
||||
|
||||
/// @brief return a string representation of the JSON pointer
|
||||
@@ -137,29 +129,21 @@ class json_pointer
|
||||
friend json_pointer operator/(const json_pointer& lhs,
|
||||
const json_pointer& 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;
|
||||
return json_pointer(lhs) /= rhs;
|
||||
}
|
||||
|
||||
/// @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)
|
||||
{
|
||||
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;
|
||||
return json_pointer(lhs) /= std::move(token);
|
||||
}
|
||||
|
||||
/// @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 lhs / detail::to_string<string_t>(array_idx);
|
||||
return json_pointer(lhs) /= array_idx;
|
||||
}
|
||||
|
||||
/// @brief returns the parent of this JSON pointer
|
||||
@@ -171,9 +155,8 @@ class json_pointer
|
||||
return *this;
|
||||
}
|
||||
|
||||
json_pointer res;
|
||||
res.reference_tokens.reserve(reference_tokens.size() - 1);
|
||||
res.reference_tokens.insert(res.reference_tokens.end(), reference_tokens.begin(), reference_tokens.end() - 1);
|
||||
json_pointer res = *this;
|
||||
res.pop_back();
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -261,76 +244,21 @@ class json_pointer
|
||||
}
|
||||
|
||||
private:
|
||||
// The throw sites below are kept out of line, so that the hot functions neither
|
||||
// contain the message construction nor the cleanup code that comes with it.
|
||||
|
||||
/// @throw out_of_range.405 always
|
||||
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_no_parent()
|
||||
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_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context)
|
||||
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);
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_unresolved, detail::concat("unresolved reference token '", reference_token, "'"), context));
|
||||
}
|
||||
|
||||
/// @throw out_of_range.402 always
|
||||
template<typename BasicJsonContext>
|
||||
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_past_the_end(std::size_t size, BasicJsonContext context)
|
||||
{
|
||||
static_cast<void>(size); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(context);
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(size), ") is out of range"), context));
|
||||
}
|
||||
|
||||
/// @throw out_of_range.401 always
|
||||
template<typename BasicJsonContext>
|
||||
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_index_out_of_range(std::size_t idx, BasicJsonContext context)
|
||||
{
|
||||
static_cast<void>(idx); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(context);
|
||||
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"), context));
|
||||
}
|
||||
|
||||
/// @throw parse_error.106 always
|
||||
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_leading_zero(const string_t& s)
|
||||
{
|
||||
static_cast<void>(s); // unused when JSON_NOEXCEPTION is defined
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr));
|
||||
}
|
||||
|
||||
/// @throw parse_error.109 always
|
||||
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_not_a_number(const string_t& s)
|
||||
{
|
||||
static_cast<void>(s); // unused when JSON_NOEXCEPTION is defined
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", s, "' is not a number"), nullptr));
|
||||
}
|
||||
|
||||
/// @throw out_of_range.410 always
|
||||
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_exceeds_size_type(const string_t& s)
|
||||
{
|
||||
static_cast<void>(s); // unused when JSON_NOEXCEPTION is defined
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::value_out_of_range, detail::concat("array index ", s, " exceeds size_type"), nullptr));
|
||||
}
|
||||
|
||||
/// @throw parse_error.107 always
|
||||
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_missing_slash(const string_t& reference_string)
|
||||
{
|
||||
static_cast<void>(reference_string); // unused when JSON_NOEXCEPTION is defined
|
||||
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));
|
||||
}
|
||||
|
||||
/// @throw parse_error.108 always
|
||||
JSON_HEDLEY_NEVER_INLINE JSON_HEDLEY_NO_RETURN static void throw_invalid_escape()
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief result of @ref parse_array_index
|
||||
|
||||
@@ -377,30 +305,6 @@ 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;
|
||||
bool all_digits = true;
|
||||
for (std::size_t i = 0; i < s.size(); ++i)
|
||||
{
|
||||
const char c = s[i];
|
||||
if (JSON_HEDLEY_UNLIKELY(c < '0' || c > '9'))
|
||||
{
|
||||
all_digits = false;
|
||||
break;
|
||||
}
|
||||
acc = static_cast<size_type>(acc * 10 + static_cast<size_type>(c - '0'));
|
||||
}
|
||||
if (JSON_HEDLEY_LIKELY(all_digits))
|
||||
{
|
||||
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
|
||||
@@ -439,14 +343,16 @@ class json_pointer
|
||||
typename BasicJsonType::size_type idx{};
|
||||
switch (parse_array_index<BasicJsonType>(s, idx))
|
||||
{
|
||||
// the branches differ in their messages, not after JSON_THROW's expansion
|
||||
// NOLINTNEXTLINE(bugprone-branch-clone)
|
||||
case array_index_status::leading_zero:
|
||||
throw_leading_zero(s);
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", s, "' must not begin with '0'"), nullptr));
|
||||
case array_index_status::not_a_number:
|
||||
throw_not_a_number(s);
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", s, "' is not a number"), nullptr));
|
||||
case array_index_status::unresolved:
|
||||
throw_unresolved(s, nullptr);
|
||||
case array_index_status::exceeds_size_type:
|
||||
throw_exceeds_size_type(s);
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::value_out_of_range, detail::concat("array index ", s, " exceeds size_type"), nullptr));
|
||||
case array_index_status::ok:
|
||||
default:
|
||||
break;
|
||||
@@ -455,83 +361,6 @@ 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] != '/'))
|
||||
{
|
||||
throw_missing_slash(reference_string);
|
||||
}
|
||||
|
||||
// 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')))
|
||||
{
|
||||
throw_invalid_escape();
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
{
|
||||
@@ -575,26 +404,23 @@ class json_pointer
|
||||
|
||||
// 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, const string_t& reference_token)
|
||||
std::size_t add_child(std::size_t id, string_t&& reference_token)
|
||||
{
|
||||
if (reference_token == "0")
|
||||
{
|
||||
is_array[id] = true;
|
||||
}
|
||||
|
||||
// shared prefixes are found without copying the token
|
||||
const auto found = children[id].find(reference_token);
|
||||
if (found != children[id].end())
|
||||
{
|
||||
return found->second;
|
||||
}
|
||||
|
||||
// read the number before the emplace_back below, which may
|
||||
// reallocate children and invalidate the iterator
|
||||
const std::size_t child = children.size();
|
||||
children[id].emplace(reference_token, child);
|
||||
children.emplace_back();
|
||||
is_array.push_back(false);
|
||||
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)
|
||||
{
|
||||
children.emplace_back();
|
||||
is_array.push_back(false);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
@@ -652,7 +478,7 @@ class json_pointer
|
||||
case detail::value_t::object:
|
||||
{
|
||||
// create an entry in the object
|
||||
result = &object_member(*result, reference_token);
|
||||
result = &result->operator[](reference_token);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -732,7 +558,7 @@ class json_pointer
|
||||
case detail::value_t::object:
|
||||
{
|
||||
// use unchecked object access
|
||||
ptr = &object_member(*ptr, reference_token);
|
||||
ptr = &ptr->operator[](reference_token);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -782,13 +608,8 @@ class json_pointer
|
||||
{
|
||||
case detail::value_t::object:
|
||||
{
|
||||
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;
|
||||
// note: at performs range check
|
||||
ptr = &ptr->at(reference_token);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -797,16 +618,19 @@ class json_pointer
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
throw_past_the_end(ptr->m_data.m_value.array->size(), ptr);
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat(
|
||||
"array index '-' (", std::to_string(ptr->m_data.m_value.array->size()),
|
||||
") is out of range"), ptr));
|
||||
}
|
||||
|
||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||
// Bounds check before access to avoid exception with JSON_NOEXCEPTION
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
throw_index_out_of_range(idx, ptr);
|
||||
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->m_data.m_value.array)[idx];
|
||||
ptr = &ptr->operator[](idx);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -854,9 +678,7 @@ class json_pointer
|
||||
{
|
||||
// use unchecked object access; the const operator[]
|
||||
// asserts that the key exists
|
||||
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;
|
||||
ptr = &ptr->operator[](reference_token);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -865,14 +687,12 @@ class json_pointer
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
{
|
||||
// "-" cannot be used for const access
|
||||
throw_past_the_end(ptr->m_data.m_value.array->size(), ptr);
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
|
||||
}
|
||||
|
||||
// use unchecked array access; the const operator[]
|
||||
// asserts that the index exists
|
||||
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];
|
||||
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -913,12 +733,12 @@ class json_pointer
|
||||
{
|
||||
case detail::value_t::object:
|
||||
{
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
const auto it = ptr->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->end()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &it->second;
|
||||
ptr = &*it;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -937,10 +757,12 @@ class json_pointer
|
||||
typename BasicJsonType::size_type idx{};
|
||||
switch (parse_array_index<BasicJsonType>(reference_token, idx))
|
||||
{
|
||||
// the branches differ in their messages, not after JSON_THROW's expansion
|
||||
// NOLINTNEXTLINE(bugprone-branch-clone)
|
||||
case array_index_status::leading_zero:
|
||||
throw_leading_zero(reference_token);
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_leading_zero, 0, detail::concat("array index '", reference_token, "' must not begin with '0'"), nullptr));
|
||||
case array_index_status::not_a_number:
|
||||
throw_not_a_number(reference_token);
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_index_not_a_number, 0, detail::concat("array index '", reference_token, "' is not a number"), nullptr));
|
||||
case array_index_status::unresolved:
|
||||
case array_index_status::exceeds_size_type:
|
||||
return nullptr;
|
||||
@@ -953,7 +775,7 @@ class json_pointer
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
ptr = &ptr->operator[](idx);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -986,14 +808,13 @@ class json_pointer
|
||||
{
|
||||
case detail::value_t::object:
|
||||
{
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
if (it == ptr->m_data.m_value.object->end())
|
||||
if (!ptr->contains(reference_token))
|
||||
{
|
||||
// we did not find the key in the object
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = &it->second;
|
||||
ptr = &ptr->operator[](reference_token);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1015,13 +836,13 @@ class json_pointer
|
||||
return false;
|
||||
}
|
||||
|
||||
if (idx >= ptr->m_data.m_value.array->size())
|
||||
if (idx >= ptr->size())
|
||||
{
|
||||
// index out of range
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
ptr = &ptr->operator[](idx);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1068,18 +889,9 @@ class json_pointer
|
||||
// check if a nonempty reference string begins with slash
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
|
||||
{
|
||||
throw_missing_slash(reference_string);
|
||||
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
|
||||
@@ -1101,29 +913,25 @@ 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; 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)
|
||||
// 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))
|
||||
{
|
||||
for (; pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
|
||||
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_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')))
|
||||
{
|
||||
throw_invalid_escape();
|
||||
}
|
||||
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
|
||||
result.push_back(std::move(reference_token));
|
||||
detail::unescape(reference_token);
|
||||
result.push_back(reference_token);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -1289,22 +1097,18 @@ class json_pointer
|
||||
// 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)
|
||||
// (each key is parsed once and the pointer kept for the second pass)
|
||||
prefix_tree tree;
|
||||
std::vector<json_pointer> pointers;
|
||||
pointers.reserve(value.m_data.m_value.object->size());
|
||||
for (const auto& element : *value.m_data.m_value.object)
|
||||
{
|
||||
pointers.emplace_back(element.first);
|
||||
json_pointer ptr(element.first);
|
||||
std::size_t id = 0;
|
||||
for (const auto& reference_token : pointers.back().reference_tokens)
|
||||
for (auto& reference_token : ptr.reference_tokens)
|
||||
{
|
||||
id = tree.add_child(id, reference_token);
|
||||
id = tree.add_child(id, std::move(reference_token));
|
||||
}
|
||||
}
|
||||
|
||||
// iterate the JSON object values
|
||||
std::size_t index = 0;
|
||||
for (const auto& element : *value.m_data.m_value.object)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))
|
||||
@@ -1316,7 +1120,7 @@ class json_pointer
|
||||
// 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.
|
||||
pointers[index++].get_and_create(result, tree) = element.second;
|
||||
json_pointer(element.first).get_and_create(result, tree) = element.second;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -1355,7 +1159,7 @@ class json_pointer
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return equals_string(rhs);
|
||||
return *this == json_pointer(rhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1439,7 +1243,7 @@ inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return lhs.equals_string(rhs);
|
||||
return lhs == json_pointer<RefStringTypeLhs>(rhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1452,7 +1256,7 @@ inline bool operator==(const StringType& lhs,
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return rhs.equals_string(lhs);
|
||||
return json_pointer<RefStringTypeRhs>(lhs) == rhs;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -683,6 +683,22 @@ struct is_compatible_integer_type
|
||||
: is_compatible_integer_type_impl<RealIntegerType,
|
||||
CompatibleNumberIntegerType> {};
|
||||
|
||||
// whether to_json() stores an integer of type CompatibleNumberIntegerType as
|
||||
// number_integer
|
||||
template<typename BasicJsonType, typename CompatibleNumberIntegerType, typename = void>
|
||||
struct is_compatible_number_integer_type
|
||||
: is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType> {};
|
||||
|
||||
// if number_unsigned_t is the same type as number_integer_t, unsigned integers
|
||||
// are stored as number_integer as well (#2573)
|
||||
template<typename BasicJsonType, typename CompatibleNumberIntegerType>
|
||||
struct is_compatible_number_integer_type<BasicJsonType, CompatibleNumberIntegerType,
|
||||
enable_if_t<conjunction<std::is_same<typename BasicJsonType::number_integer_t, typename BasicJsonType::number_unsigned_t>,
|
||||
std::is_integral<CompatibleNumberIntegerType>,
|
||||
std::is_unsigned<CompatibleNumberIntegerType>,
|
||||
negation<std::is_same<bool, CompatibleNumberIntegerType>>>::value>>
|
||||
: std::is_constructible<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType> {};
|
||||
|
||||
template<typename BasicJsonType, typename CompatibleType, typename = void>
|
||||
struct is_compatible_type_impl: std::false_type {};
|
||||
|
||||
@@ -1020,6 +1036,25 @@ constexpr bool value_in_range_of(T val)
|
||||
template<bool Value>
|
||||
using bool_constant = std::integral_constant<bool, Value>;
|
||||
|
||||
// Stands in for number_unsigned_t in overload sets if it is the same type as
|
||||
// number_integer_t, so that the unsigned overloads do not collide with the
|
||||
// integer ones. Never constructed. Such a basic_json stores every integer as
|
||||
// number_integer (#2573).
|
||||
struct same_as_number_integer_type {};
|
||||
|
||||
// NumberUnsignedType, or same_as_number_integer_type if it is the same type as
|
||||
// NumberIntegerType; basic_json uses this one, as it cannot name its own member
|
||||
// types through an incomplete basic_json (MSVC reports a recursive alias, and
|
||||
// old GCC versions crash)
|
||||
template<typename NumberIntegerType, typename NumberUnsignedType>
|
||||
using number_unsigned_overload_type = typename std::conditional<std::is_same<NumberIntegerType, NumberUnsignedType>::value,
|
||||
same_as_number_integer_type, NumberUnsignedType>::type;
|
||||
|
||||
// number_unsigned_t of BasicJsonType, or same_as_number_integer_type if it is
|
||||
// the same type as number_integer_t
|
||||
template<typename BasicJsonType>
|
||||
using number_unsigned_overload_t = number_unsigned_overload_type<typename BasicJsonType::number_integer_t, typename BasicJsonType::number_unsigned_t>;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// is_c_string
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -221,7 +221,7 @@ class binary_writer
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
write_cbor_head(0x00, j.m_data.m_value.number_unsigned);
|
||||
write_cbor_head(0x00, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1601,7 +1601,7 @@ class binary_writer
|
||||
return calc_bson_integer_size(j.m_data.m_value.number_integer);
|
||||
|
||||
case value_t::number_unsigned:
|
||||
return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);
|
||||
return calc_bson_unsigned_size(conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
|
||||
case value_t::string:
|
||||
return calc_bson_string_size(*j.m_data.m_value.string, j);
|
||||
@@ -1643,7 +1643,7 @@ class binary_writer
|
||||
return write_bson_integer(name, j.m_data.m_value.number_integer);
|
||||
|
||||
case value_t::number_unsigned:
|
||||
return write_bson_unsigned(name, j.m_data.m_value.number_unsigned);
|
||||
return write_bson_unsigned(name, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
|
||||
case value_t::string:
|
||||
return write_bson_string(name, *j.m_data.m_value.string);
|
||||
|
||||
@@ -49,6 +49,12 @@ class serializer
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
/// type of the absolute value of an integer: number_unsigned_t, or its
|
||||
/// unsigned counterpart if it is signed (it may be number_integer_t, #2573)
|
||||
using number_magnitude_t = typename std::conditional <
|
||||
std::is_integral<number_unsigned_t>::value && std::is_signed<number_unsigned_t>::value,
|
||||
std::make_unsigned<number_unsigned_t>,
|
||||
std::enable_if<true, number_unsigned_t >>::type::type;
|
||||
using binary_char_t = typename BasicJsonType::binary_t::value_type;
|
||||
|
||||
public:
|
||||
@@ -1227,7 +1233,7 @@ class serializer
|
||||
@param[in] x unsigned integer number to count its digits
|
||||
@return number of decimal digits
|
||||
*/
|
||||
unsigned int count_digits(number_unsigned_t x) noexcept
|
||||
unsigned int count_digits(number_magnitude_t x) noexcept
|
||||
{
|
||||
unsigned int n_digits = 1;
|
||||
for (;;)
|
||||
@@ -1410,7 +1416,7 @@ class serializer
|
||||
}
|
||||
auto* buffer_ptr = write_buffer.data() + write_buffer_pos;
|
||||
|
||||
number_unsigned_t abs_value;
|
||||
number_magnitude_t abs_value;
|
||||
|
||||
// one byte for the minus sign
|
||||
unsigned int n_chars = 0;
|
||||
@@ -1423,7 +1429,7 @@ class serializer
|
||||
}
|
||||
else
|
||||
{
|
||||
abs_value = static_cast<number_unsigned_t>(x);
|
||||
abs_value = static_cast<number_magnitude_t>(x);
|
||||
}
|
||||
|
||||
// up to 16 digits: eight at a time (as the digits of floats), written
|
||||
@@ -1591,17 +1597,6 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Overload to make the compiler happy while it is instantiating
|
||||
* dump_integer for number_unsigned_t.
|
||||
* Must never be called.
|
||||
*/
|
||||
number_unsigned_t remove_sign(number_unsigned_t x)
|
||||
{
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
return x; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function for dump_integer
|
||||
*
|
||||
@@ -1611,10 +1606,10 @@ class serializer
|
||||
* absolute values of INT_MIN and INT_MAX are usually not the same. See
|
||||
* #1708 for details.
|
||||
*/
|
||||
number_unsigned_t remove_sign(number_integer_t x) noexcept
|
||||
number_magnitude_t remove_sign(number_integer_t x) noexcept
|
||||
{
|
||||
JSON_ASSERT(x < 0);
|
||||
return static_cast<number_unsigned_t>(-(x + 1)) + 1;
|
||||
return static_cast<number_magnitude_t>(-(x + 1)) + 1;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <utility> // move
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
|
||||
@@ -25,46 +24,39 @@ 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, 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.
|
||||
* 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.
|
||||
*/
|
||||
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)
|
||||
{
|
||||
// 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] != '/')
|
||||
auto next_special = [&s](std::size_t from)
|
||||
{
|
||||
++i;
|
||||
}
|
||||
if (i == size)
|
||||
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)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
StringType result;
|
||||
result.reserve(size + 2);
|
||||
escape_into(result, s);
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -109,7 +101,7 @@ inline void unescape(StringType& s)
|
||||
pos = s.find_first_of('~', run);
|
||||
}
|
||||
result.append(s.data() + run, s.size() - run);
|
||||
s = std::move(result);
|
||||
s = result;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
+50
-28
@@ -500,8 +500,22 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
"NumberUnsignedType must be at least as wide as NumberIntegerType, "
|
||||
"because it has to hold the absolute value of every NumberIntegerType value");
|
||||
|
||||
// NumberUnsignedType may be the same type as NumberIntegerType (#2573), but
|
||||
// a repeated NumberFloatType would give overloads that collide, so it gets
|
||||
// a readable diagnostic instead.
|
||||
static_assert(!std::is_same<NumberFloatType, NumberIntegerType>::value && !std::is_same<NumberFloatType, NumberUnsignedType>::value,
|
||||
"NumberFloatType must be a different type than NumberIntegerType and NumberUnsignedType");
|
||||
|
||||
private:
|
||||
|
||||
/// the kind a value of kind @a t is stored as: if number_unsigned_t is the
|
||||
/// same type as number_integer_t, every integer is stored as number_integer
|
||||
/// (#2573); every other kind is stored as itself
|
||||
static constexpr value_t stored_kind(const value_t t) noexcept
|
||||
{
|
||||
return (std::is_same<number_integer_t, number_unsigned_t>::value && t == value_t::number_unsigned) ? value_t::number_integer : t;
|
||||
}
|
||||
|
||||
/// helper for exception-safe object creation
|
||||
template<typename T, typename... Args>
|
||||
JSON_HEDLEY_RETURNS_NON_NULL
|
||||
@@ -575,8 +589,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
json_value(boolean_t v) noexcept : boolean(v) {}
|
||||
/// constructor for numbers (integer)
|
||||
json_value(number_integer_t v) noexcept : number_integer(v) {}
|
||||
/// constructor for numbers (unsigned)
|
||||
json_value(number_unsigned_t v) noexcept : number_unsigned(v) {}
|
||||
/// constructor for numbers (unsigned); takes a placeholder if number_unsigned_t is number_integer_t
|
||||
json_value(detail::number_unsigned_overload_type<NumberIntegerType, NumberUnsignedType> v) noexcept : number_unsigned(v) {}
|
||||
/// constructor for numbers (floating-point)
|
||||
json_value(number_float_t v) noexcept : number_float(v) {}
|
||||
/// constructor for empty values of a given type
|
||||
@@ -2181,7 +2195,7 @@ public:
|
||||
/// @brief create an empty value with a given type
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
|
||||
basic_json(const value_t v)
|
||||
: m_data(v)
|
||||
: m_data(stored_kind(v))
|
||||
{
|
||||
assert_invariant();
|
||||
}
|
||||
@@ -2262,7 +2276,7 @@ public:
|
||||
{
|
||||
convert_leaf(val);
|
||||
}
|
||||
JSON_ASSERT(m_data.m_type == val.type());
|
||||
JSON_ASSERT(m_data.m_type == stored_kind(val.type()));
|
||||
|
||||
set_parents();
|
||||
assert_invariant();
|
||||
@@ -2847,14 +2861,16 @@ public:
|
||||
return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr;
|
||||
}
|
||||
|
||||
/// get a pointer to the value (unsigned number)
|
||||
number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept
|
||||
/// get a pointer to the value (unsigned number); takes a placeholder if
|
||||
/// number_unsigned_t is number_integer_t (the integer overload handles it then)
|
||||
number_unsigned_t* get_impl_ptr(detail::number_unsigned_overload_type<NumberIntegerType, NumberUnsignedType>* /*unused*/) noexcept
|
||||
{
|
||||
return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr;
|
||||
}
|
||||
|
||||
/// get a pointer to the value (unsigned number)
|
||||
constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept
|
||||
/// get a pointer to the value (unsigned number); takes a placeholder if
|
||||
/// number_unsigned_t is number_integer_t (the integer overload handles it then)
|
||||
constexpr const number_unsigned_t* get_impl_ptr(const detail::number_unsigned_overload_type<NumberIntegerType, NumberUnsignedType>* /*unused*/) const noexcept
|
||||
{
|
||||
return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr;
|
||||
}
|
||||
@@ -6562,18 +6578,24 @@ public:
|
||||
};
|
||||
|
||||
// wrapper for "add" operation; add value at ptr
|
||||
const auto operation_add = [&result](json_pointer & ptr, basic_json val)
|
||||
const auto operation_add = [&result](json_pointer & ptr, const basic_json & val)
|
||||
{
|
||||
// adding to the root of the target document means replacing it
|
||||
if (ptr.empty())
|
||||
{
|
||||
result = std::move(val);
|
||||
result = val;
|
||||
return;
|
||||
}
|
||||
|
||||
// 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());
|
||||
// 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();
|
||||
ptr.pop_back();
|
||||
// parent must exist when performing patch add per RFC6902 specs
|
||||
basic_json& parent = result.at(ptr);
|
||||
@@ -6584,7 +6606,7 @@ public:
|
||||
case value_t::object:
|
||||
{
|
||||
// use operator[] to add value
|
||||
parent[std::move(last_path)] = std::move(val);
|
||||
parent[last_path] = val;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -6593,7 +6615,7 @@ public:
|
||||
if (last_path == "-")
|
||||
{
|
||||
// special case: append to back
|
||||
parent.push_back(std::move(val));
|
||||
parent.push_back(val);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -6605,7 +6627,7 @@ public:
|
||||
}
|
||||
|
||||
// default case: insert add offset
|
||||
parent.insert(parent.begin() + static_cast<difference_type>(idx), std::move(val));
|
||||
parent.insert(parent.begin() + static_cast<difference_type>(idx), val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -6628,8 +6650,7 @@ public:
|
||||
const auto operation_remove = [this, & result](json_pointer & ptr)
|
||||
{
|
||||
// get reference to the parent of the JSON pointer ptr
|
||||
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());
|
||||
const auto last_path = ptr.back();
|
||||
ptr.pop_back();
|
||||
basic_json& parent = result.at(ptr);
|
||||
|
||||
@@ -6729,8 +6750,8 @@ public:
|
||||
}
|
||||
|
||||
// collect mandatory members
|
||||
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;
|
||||
const auto op = get_value("op", "op", true).template get<string_t>();
|
||||
const auto path = get_value(op, "path", true).template get<string_t>();
|
||||
json_pointer ptr(path);
|
||||
|
||||
switch (get_op(op))
|
||||
@@ -6756,7 +6777,7 @@ public:
|
||||
|
||||
case patch_operations::move:
|
||||
{
|
||||
const string_t& from_path = *get_value("move", "from", true).m_data.m_value.string;
|
||||
const auto from_path = get_value("move", "from", true).template get<string_t>();
|
||||
json_pointer from_ptr(from_path);
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
|
||||
@@ -6765,28 +6786,29 @@ public:
|
||||
}
|
||||
|
||||
// the "from" location must exist - use at()
|
||||
// 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));
|
||||
basic_json const v = 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, std::move(v));
|
||||
operation_add(ptr, v);
|
||||
break;
|
||||
}
|
||||
|
||||
case patch_operations::copy:
|
||||
{
|
||||
const string_t& from_path = *get_value("copy", "from", true).m_data.m_value.string;
|
||||
const auto from_path = get_value("copy", "from", true).template get<string_t>();
|
||||
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; the "from" location
|
||||
// must exist - use at()
|
||||
operation_add(ptr, result.at(from_ptr));
|
||||
// specified in the "from" member.
|
||||
operation_add(ptr, v);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
Generated
+228
-356
File diff suppressed because it is too large.
Load diff
@@ -23,14 +23,8 @@ 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")
|
||||
{
|
||||
@@ -1077,193 +1071,3 @@ 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})");
|
||||
}
|
||||
}
|
||||
|
||||
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,201 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// basic_json with the same type for number_integer_t and number_unsigned_t
|
||||
// (#2573): every integer is stored as number_integer
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template<typename IntegerType>
|
||||
using same_types_json = nlohmann::json::with_integers_t<IntegerType, IntegerType>;
|
||||
|
||||
using json64 = same_types_json<std::int64_t>;
|
||||
using json32 = same_types_json<std::int32_t>;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE_TEMPLATE("same type for number_integer_t and number_unsigned_t", Json, json64, json32)
|
||||
{
|
||||
using integer_t = typename Json::number_integer_t;
|
||||
using limits = std::numeric_limits<integer_t>;
|
||||
|
||||
SECTION("parsing stores integers as number_integer")
|
||||
{
|
||||
const Json j = Json::parse("[0, 1, -1, 2147483647, -2147483648]");
|
||||
for (const auto& element : j)
|
||||
{
|
||||
CHECK(element.is_number_integer());
|
||||
CHECK(!element.is_number_unsigned());
|
||||
CHECK(element.type() == nlohmann::detail::value_t::number_integer);
|
||||
}
|
||||
CHECK(j[1].template get<integer_t>() == 1);
|
||||
CHECK(j[2].template get<integer_t>() == -1);
|
||||
CHECK(j[3].template get<integer_t>() == 2147483647);
|
||||
CHECK(j[4].template get<std::int64_t>() == -2147483648LL);
|
||||
}
|
||||
|
||||
SECTION("integers above the maximum are parsed as floats")
|
||||
{
|
||||
// a positive literal is parsed as unsigned first; it must not wrap
|
||||
// around to a negative number_integer_t
|
||||
const auto above_max = std::to_string(static_cast<std::uint64_t>(limits::max()) + 1);
|
||||
const Json j1 = Json::parse(above_max);
|
||||
CHECK(j1.is_number_float());
|
||||
CHECK(j1.template get<double>() == static_cast<double>(static_cast<std::uint64_t>(limits::max()) + 1));
|
||||
|
||||
const Json j2 = Json::parse("18446744073709551615");
|
||||
CHECK(j2.is_number_float());
|
||||
CHECK(j2.template get<double>() == 18446744073709551615.0);
|
||||
|
||||
const Json j3 = Json::parse(std::to_string(limits::max()));
|
||||
CHECK(j3.is_number_integer());
|
||||
CHECK(j3.template get<integer_t>() == limits::max());
|
||||
}
|
||||
|
||||
SECTION("serialization of the extremes")
|
||||
{
|
||||
CHECK(Json(limits::max()).dump() == std::to_string(limits::max()));
|
||||
// the absolute value of the minimum does not fit into number_unsigned_t
|
||||
CHECK(Json(limits::min()).dump() == std::to_string(limits::min()));
|
||||
CHECK(Json::parse(std::to_string(limits::min())) == Json(limits::min()));
|
||||
CHECK(Json(0).dump() == "0");
|
||||
}
|
||||
|
||||
SECTION("construction from unsigned types")
|
||||
{
|
||||
const Json j1 = 5u;
|
||||
CHECK(j1.is_number_integer());
|
||||
CHECK(!j1.is_number_unsigned());
|
||||
CHECK(j1 == 5);
|
||||
|
||||
const Json j2 = static_cast<std::uint8_t>(200);
|
||||
CHECK(j2.is_number_integer());
|
||||
CHECK(j2.template get<integer_t>() == 200);
|
||||
|
||||
const Json j3 = std::vector<unsigned> {1, 2, 3};
|
||||
CHECK(j3.dump() == "[1,2,3]");
|
||||
CHECK(j3[0].is_number_integer());
|
||||
|
||||
const Json j4 = true;
|
||||
CHECK(j4.is_boolean());
|
||||
}
|
||||
|
||||
SECTION("value_t::number_unsigned creates a number_integer")
|
||||
{
|
||||
const Json j(nlohmann::detail::value_t::number_unsigned);
|
||||
CHECK(j.is_number_integer());
|
||||
CHECK(!j.is_number_unsigned());
|
||||
CHECK(j == 0);
|
||||
CHECK(j.dump() == "0");
|
||||
}
|
||||
|
||||
SECTION("conversions")
|
||||
{
|
||||
Json j = 42;
|
||||
CHECK(j.template get<unsigned>() == 42u);
|
||||
CHECK(j.template get<std::uint64_t>() == 42u);
|
||||
CHECK(j.template get<int>() == 42);
|
||||
CHECK(j.template get<double>() == 42.0);
|
||||
CHECK(j.template get_ptr<integer_t*>() != nullptr);
|
||||
CHECK(*j.template get_ptr<integer_t*>() == 42);
|
||||
CHECK(j.template get_ref<integer_t&>() == 42);
|
||||
j.template get_ref<integer_t&>() = 43;
|
||||
CHECK(j == 43);
|
||||
|
||||
integer_t i = 0;
|
||||
j.get_to(i);
|
||||
CHECK(i == 43);
|
||||
|
||||
const Json object = {{"a", 1}};
|
||||
CHECK(object.value("a", 0u) == 1u);
|
||||
CHECK(object.value("b", 7u) == 7u);
|
||||
}
|
||||
|
||||
SECTION("conversion from a basic_json with distinct types")
|
||||
{
|
||||
const nlohmann::json j = {1, 2u, -3, 4.5};
|
||||
CHECK(j[1].is_number_unsigned());
|
||||
const Json converted = j;
|
||||
CHECK(converted.dump() == "[1,2,-3,4.5]");
|
||||
CHECK(converted[1].is_number_integer());
|
||||
CHECK(!converted[1].is_number_unsigned());
|
||||
|
||||
const nlohmann::json back = converted;
|
||||
CHECK(back == j);
|
||||
}
|
||||
|
||||
SECTION("comparison and hashing")
|
||||
{
|
||||
const Json j1 = 1;
|
||||
const Json j2 = 2u;
|
||||
CHECK(j1 < j2);
|
||||
CHECK(j1 != j2);
|
||||
CHECK(j2 == 2);
|
||||
CHECK(j2 == 2u);
|
||||
CHECK(j1 < 1.5);
|
||||
CHECK(Json(-1) < Json(0u));
|
||||
CHECK(std::hash<Json> {}(Json(5u)) == std::hash<Json> {}(Json(5)));
|
||||
}
|
||||
|
||||
SECTION("JSON Patch, diff, and flatten")
|
||||
{
|
||||
const Json source = Json::parse(R"({"a": [1, 2, 3], "b": 4})");
|
||||
const Json target = Json::parse(R"({"a": [1, 5], "c": 6})");
|
||||
CHECK(source.patch(Json::diff(source, target)) == target);
|
||||
CHECK(source.flatten().unflatten() == source);
|
||||
}
|
||||
|
||||
SECTION("binary formats")
|
||||
{
|
||||
const Json j = {{"min", limits::min()}, {"max", limits::max()}, {"zero", 0}, {"small", 100}, {"negative", -100}, {"float", 1.5}};
|
||||
|
||||
CHECK(Json::from_cbor(Json::to_cbor(j)) == j);
|
||||
CHECK(Json::from_msgpack(Json::to_msgpack(j)) == j);
|
||||
CHECK(Json::from_ubjson(Json::to_ubjson(j)) == j);
|
||||
CHECK(Json::from_bjdata(Json::to_bjdata(j)) == j);
|
||||
CHECK(Json::from_bson(Json::to_bson(j)) == j);
|
||||
CHECK(Json::from_bon8(Json::to_bon8(j)) == j);
|
||||
|
||||
for (const auto& element : Json::from_cbor(Json::to_cbor(j)))
|
||||
{
|
||||
CHECK(!element.is_number_unsigned());
|
||||
}
|
||||
|
||||
// CBOR unsigned integer 2^64-1 does not fit and is read as float
|
||||
const std::vector<std::uint8_t> cbor_max = {0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
const Json from_cbor = Json::from_cbor(cbor_max);
|
||||
CHECK(from_cbor.is_number_float());
|
||||
CHECK(from_cbor.template get<double>() == 18446744073709551615.0);
|
||||
|
||||
// MessagePack uint 8 is read as number_integer
|
||||
const std::vector<std::uint8_t> msgpack_uint8 = {0xCC, 0xC8};
|
||||
const Json from_msgpack = Json::from_msgpack(msgpack_uint8);
|
||||
CHECK(from_msgpack.is_number_integer());
|
||||
CHECK(from_msgpack == 200);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("same type for number_integer_t and number_unsigned_t: type traits")
|
||||
{
|
||||
CHECK(std::is_same<nlohmann::detail::number_unsigned_overload_t<json64>, nlohmann::detail::same_as_number_integer_type>::value);
|
||||
CHECK(std::is_same<nlohmann::detail::number_unsigned_overload_t<nlohmann::json>, std::uint64_t>::value);
|
||||
CHECK(nlohmann::detail::is_compatible_number_integer_type<json64, unsigned>::value);
|
||||
CHECK(!nlohmann::detail::is_compatible_number_integer_type<nlohmann::json, unsigned>::value);
|
||||
CHECK(!nlohmann::detail::is_compatible_number_integer_type<json64, bool>::value);
|
||||
}
|
||||
Reference in new issue
Block a user