Compare commits

..
Author SHA1 Message Date
Niels Lohmann 77ebf7f9e1 Merge branch 'develop' into same-number-types
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 17:53:56 +02:00
Niels Lohmann 2cac35c6ad Address review: conjunction/negation trait, astyle-friendly alias, docs table order
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 17:47:38 +02:00
Niels Lohmann d5e51d03c0 Fix CI: name the unsigned overload type through template parameters
basic_json used number_unsigned_overload_t<basic_json> while the class was
still incomplete. MSVC rejected it as a recursive alias declaration and GCC
4.9, 5 and 7 crashed with an internal compiler error. basic_json now uses
number_unsigned_overload_type<NumberIntegerType, NumberUnsignedType>, which
also makes the GCC 5 workaround in the patch test unnecessary.

Infer reported a null dereference for the number_unsigned case in
from_json, which cannot happen for a basic_json whose number_unsigned_t is
number_integer_t; assert it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-14 08:41:58 +02:00
Niels Lohmann ffa987ac40 ✅ skip patch test on GCC 5 to avoid internal compiler error
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-13 21:18:20 +02:00
Niels Lohmann 735a20e36d Use json::with_integers_t in the test and the docs
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-13 18:57:59 +02:00
Niels Lohmann 45b837885e Allow the same type for number_integer_t and number_unsigned_t
basic_json did not compile with NumberUnsignedType == NumberIntegerType
because overloads on both types collided. Such a basic_json now stores
every integer as number_integer: the colliding overloads take a
placeholder type, unsigned C++ integers are converted to number_integer_t,
and value_t::number_unsigned maps to number_integer.

Also fixes two problems this configuration would hit: the unsigned
integer parser accepted values that wrap around to negative numbers, and
dump() overflowed when computing the absolute value of the minimum.

A repeated NumberFloatType gets a static_assert.

Refs #2573

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-13 18:57:07 +02:00
30 changed files with 780 additions and 1733 deletions

No files matched your search

+1 -1
View File
@@ -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
+1 -11
View File
@@ -68,14 +68,6 @@ 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.
@@ -244,9 +236,7 @@ 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. **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.
JSON value.
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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+8 -8
View File
@@ -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`
-9
View File
@@ -9,15 +9,6 @@ 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.
@@ -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);
}
@@ -266,22 +270,6 @@ 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,
@@ -291,15 +279,17 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p
j.template get<typename ConstructibleArrayType::value_type>(),
void())
{
using std::end;
ConstructibleArrayType ret;
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)
from_json_array_reserve(ret, j.size(), priority_tag<1> {});
std::transform(j.begin(), j.end(),
std::inserter(ret, end(ret)), [](const BasicJsonType & i)
{
// get<BasicJsonType>() returns *this, this won't call a from_json
// method when value_type is BasicJsonType
from_json_array_append(ret, elem.template get<typename ConstructibleArrayType::value_type>(), priority_tag<1> {});
}
return i.template get<typename ConstructibleArrayType::value_type>();
});
arr = std::move(ret);
}
@@ -397,13 +387,9 @@ 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)
{
// 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>()));
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
}
obj = std::move(ret);
}
@@ -424,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:
@@ -167,17 +167,6 @@ 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)
{
@@ -228,17 +217,6 @@ 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)
@@ -325,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));
@@ -376,66 +354,6 @@ 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
@@ -509,20 +427,6 @@ 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)
{
+3 -6
View File
@@ -674,19 +674,16 @@ class json_sax_dom_callback_parser
if (keep && ref_stack.back())
{
auto& obj = *ref_stack.back()->m_data.m_value.object;
// 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)
const auto it = obj.find(val);
if (it != obj.end())
{
// 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 = &(it->second);
object_element = &(obj[val] = discarded);
}
return true;
+19 -3
View File
@@ -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>());
}
/*!
+90 -346
View File
@@ -16,6 +16,8 @@
#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
@@ -67,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
@@ -136,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
@@ -170,8 +155,8 @@ class json_pointer
return *this;
}
json_pointer res;
res.reference_tokens = std::vector<string_t>(reference_tokens.begin(), reference_tokens.end() - 1);
json_pointer res = *this;
res.pop_back();
return res;
}
@@ -260,14 +245,14 @@ class json_pointer
private:
/// @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);
@@ -320,28 +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;
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
@@ -398,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] != '/'))
{
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
{
@@ -498,141 +384,84 @@ 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 (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.
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.
*/
struct prefix_tree
{
// 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;
// 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;
// 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()
: slots(16)
, parent(1, 0)
: children(1)
, 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, const string_t& reference_token)
std::size_t add_child(std::size_t id, 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);
// keep the load factor below 1/2
if (++used * 2 > slots.size())
// 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)
{
grow();
children.emplace_back();
is_array.push_back(false);
}
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
@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.
Complexity: Linear in the number of reference tokens (times the logarithm
of the number of siblings for the prefix lookup).
@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>
static BasicJsonType& get_and_create(BasicJsonType& j, const std::vector<string_t>& tokens,
const std::vector<std::size_t>& prefixes, const prefix_tree& tree)
BasicJsonType& get_and_create(BasicJsonType& j, const prefix_tree& tree) const
{
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 (std::size_t i = 0; i < tokens.size(); ++i)
for (const auto& reference_token : reference_tokens)
{
const auto& reference_token = tokens[i];
switch (result->type())
{
case detail::value_t::null:
{
if (tree.is_array[prefixes[i]])
if (tree.is_array[id])
{
// some reference token below this position is 0, so the
// value is an array
@@ -649,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;
}
@@ -676,6 +505,8 @@ 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;
@@ -727,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;
}
@@ -777,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;
}
@@ -804,7 +630,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->m_data.m_value.array)[idx];
ptr = &ptr->operator[](idx);
break;
}
@@ -852,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;
}
@@ -868,9 +692,7 @@ class json_pointer
// 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;
}
@@ -911,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;
}
@@ -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;
}
@@ -1071,15 +892,6 @@ 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
@@ -1101,52 +913,31 @@ 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')))
{
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
}
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;
}
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
@@ -1191,8 +982,7 @@ 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
auto& object = *result.m_data.m_value.object;
const auto enter = [&stack, &path, &object](const BasicJsonType & v)
const auto enter = [&stack, &path, &result](const BasicJsonType & v)
{
switch (v.type())
{
@@ -1201,7 +991,7 @@ class json_pointer
if (v.m_data.m_value.array->empty())
{
// flatten empty array as null
add_flattened(object, path, nullptr, 0);
result[path] = nullptr;
}
else
{
@@ -1215,7 +1005,7 @@ class json_pointer
if (v.m_data.m_value.object->empty())
{
// flatten empty object as null
add_flattened(object, path, nullptr, 0);
result[path] = nullptr;
}
else
{
@@ -1235,7 +1025,7 @@ class json_pointer
default:
{
// add a primitive value with its reference string
add_flattened(object, path, v, 0);
result[path] = v;
return;
}
}
@@ -1281,10 +1071,6 @@ 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();
}
/*!
@@ -1308,53 +1094,21 @@ 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;
// 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)
for (const auto& element : *value.m_data.m_value.object)
{
std::size_t common = 0;
if (previous != nullptr)
json_pointer ptr(element.first);
std::size_t id = 0;
for (auto& reference_token : ptr.reference_tokens)
{
const std::size_t limit = (std::min)(tokens.size(), previous->size());
while (common < limit && tokens[common] == (*previous)[common])
{
++common;
}
id = tree.add_child(id, std::move(reference_token));
}
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()))
@@ -1362,21 +1116,11 @@ 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.
get_and_create(result, tokens, prefixes, tree) = element.second;
json_pointer(element.first).get_and_create(result, tree) = element.second;
}
return result;
@@ -1415,7 +1159,7 @@ class json_pointer
= delete;
#else
{
return equals_string(rhs);
return *this == json_pointer(rhs);
}
#endif
@@ -1499,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
@@ -1512,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);
+11 -16
View File
@@ -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:
+24 -32
View File
@@ -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
View File
@@ -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;
}
-11
View File
@@ -143,17 +143,6 @@ 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;
+256 -572
View File
File diff suppressed because it is too large. Load diff
-26
View File
@@ -9,10 +9,7 @@
#include <benchmark/benchmark.h>
#include <nlohmann/json.hpp>
#include <fstream>
#include <array>
#include <deque>
#include <numeric>
#include <set>
#include <vector>
#include <test_data.hpp>
@@ -624,27 +621,4 @@ 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();
-1
View File
@@ -9,7 +9,6 @@
#include "doctest_compatibility.h"
#include <algorithm>
#include <numeric>
#include <nlohmann/json.hpp>
using nlohmann::json;
-245
View File
@@ -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,242 +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})");
}
}
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);
}
+201
View File
@@ -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);
}
-257
View File
@@ -1,257 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | 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
}