mirror of
https://github.com/nlohmann/json.git
synced 2026-10-14 10:17:12 +00:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5e51d03c0 | ||
|
|
ffa987ac40 | ||
|
|
735a20e36d | ||
|
|
45b837885e | ||
|
|
e839bbd1c0 | ||
|
|
aa9620fb15 | ||
|
|
f23830b65e | ||
|
|
40a62947d3 | ||
|
|
e8b747e4bc |
No files matched your search
+19
-7
@@ -610,9 +610,8 @@ foreach(SRC_FILE ${SRC_FILES})
|
||||
add_executable(single_${RELATIVE_SRC_FILE} EXCLUDE_FROM_ALL ${PROJECT_BINARY_DIR}/src_single/${RELATIVE_SRC_FILE}.cpp)
|
||||
target_include_directories(single_${RELATIVE_SRC_FILE} PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_features(single_${RELATIVE_SRC_FILE} PRIVATE cxx_std_11)
|
||||
if(RELATIVE_SRC_FILE STREQUAL "json" OR RELATIVE_SRC_FILE STREQUAL "json_literals")
|
||||
# see below: report the diagnostics of json.hpp and json_literals.hpp without --error, so they
|
||||
# do not fail the build
|
||||
if(RELATIVE_SRC_FILE STREQUAL "json")
|
||||
# see below: report the diagnostics of json.hpp without --error, so they do not fail the build
|
||||
set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${IWYU_TOOL} -Xiwyu --max_line_length=300)
|
||||
else()
|
||||
set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}")
|
||||
@@ -626,18 +625,31 @@ foreach(SRC_FILE ${SRC_FILES})
|
||||
# reporting its diagnostics (informational, via CXX_INCLUDE_WHAT_YOU_USE above) but exclude it
|
||||
# from the hard gate below so a fresh IWYU/compiler combination does not fail this target on a
|
||||
# nondeterministic suggestion for a header that already re-exports everything on purpose.
|
||||
# json_literals.hpp and json.hpp include each other on purpose (json.hpp includes it at its end
|
||||
# unless JSON_NO_AUTOMATIC_UDLS is defined), and IWYU, not following the cycle, suggests replacing
|
||||
# json.hpp with json_fwd.hpp although the literals need the complete basic_json; exclude it, too.
|
||||
if(NOT RELATIVE_SRC_FILE STREQUAL "json" AND NOT RELATIVE_SRC_FILE STREQUAL "json_literals")
|
||||
if(NOT RELATIVE_SRC_FILE STREQUAL "json")
|
||||
list(APPEND single_binaries_tus src_single/${RELATIVE_SRC_FILE}.cpp)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# The checks above look at each header on its own. Client code that includes only <nlohmann/json.hpp>
|
||||
# must not be told by include analyzers to include another nlohmann header for what it uses
|
||||
# (issue #5812): json.hpp marks those includes "IWYU pragma: export", and both IWYU and clang-tidy's
|
||||
# misc-include-cleaner (disabled in .clang-tidy for the library itself) check these clients.
|
||||
file(GLOB CONSUMER_FILES ${PROJECT_SOURCE_DIR}/tests/iwyu_consumer/*.cpp)
|
||||
foreach(CONSUMER_FILE ${CONSUMER_FILES})
|
||||
get_filename_component(CONSUMER_NAME "${CONSUMER_FILE}" NAME_WE)
|
||||
add_executable(iwyu_consumer_${CONSUMER_NAME} EXCLUDE_FROM_ALL ${CONSUMER_FILE})
|
||||
target_include_directories(iwyu_consumer_${CONSUMER_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_features(iwyu_consumer_${CONSUMER_NAME} PRIVATE cxx_std_11)
|
||||
list(APPEND single_binaries iwyu_consumer_${CONSUMER_NAME})
|
||||
list(APPEND single_binaries_tus ${CONSUMER_FILE})
|
||||
endforeach()
|
||||
|
||||
if(IWYU_TOOL_PY)
|
||||
add_custom_target(ci_single_binaries
|
||||
DEPENDS ${single_binaries}
|
||||
COMMAND ${IWYU_TOOL_PY} -p ${PROJECT_BINARY_DIR} ${single_binaries_tus} -- ${iwyu_options}
|
||||
COMMAND ${CLANG_TIDY_TOOL} -p ${PROJECT_BINARY_DIR} --checks=-*,misc-include-cleaner --warnings-as-errors=* ${CONSUMER_FILES}
|
||||
VERBATIM
|
||||
COMMENT "Check if headers are self-contained"
|
||||
)
|
||||
else()
|
||||
|
||||
@@ -59,6 +59,7 @@ Currently, only `remove`, `add`, and `replace` operations are generated.
|
||||
- [patch](patch.md) applies a JSON Patch
|
||||
- [patch_inplace](patch_inplace.md) applies a JSON Patch in place
|
||||
- [merge_patch](merge_patch.md) applies a JSON Merge Patch
|
||||
- [merge_diff](merge_diff.md) creates a diff as a JSON Merge Patch
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -285,6 +285,7 @@ Access to the JSON value
|
||||
### JSON Merge Patch functions
|
||||
|
||||
- [**merge_patch**](merge_patch.md) - applies a JSON Merge Patch
|
||||
- [**merge_diff**](merge_diff.md) (_static_) - creates a diff as a JSON Merge Patch
|
||||
|
||||
## Static functions
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# <small>nlohmann::basic_json::</small>merge_diff
|
||||
|
||||
```cpp
|
||||
static basic_json merge_diff(const basic_json& source,
|
||||
const basic_json& target);
|
||||
```
|
||||
|
||||
Creates a [JSON Merge Patch](https://tools.ietf.org/html/rfc7396) so that value `source` can be changed into the value
|
||||
`target` by calling the [`merge_patch`](merge_patch.md) function. The patch contains only what differs: changed and
|
||||
added members with their new values, and removed members with the value `#!json null`. Nested objects are compared
|
||||
member by member; any other value that differs, including an array, is replaced as a whole.
|
||||
|
||||
For two JSON values `source` and `target`, where `target` contains no object member whose value is `#!json null`, the
|
||||
following code always yields `#!cpp true`:
|
||||
```cpp
|
||||
basic_json patched = source;
|
||||
patched.merge_patch(merge_diff(source, target));
|
||||
patched == target;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
`source` (in)
|
||||
: JSON value to compare from
|
||||
|
||||
`target` (in)
|
||||
: JSON value to compare against
|
||||
|
||||
## Return value
|
||||
|
||||
a JSON Merge Patch to convert the `source` to `target`
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong guarantee: `source` and `target` are never modified.
|
||||
|
||||
## Complexity
|
||||
|
||||
Linear in the total number of values (including nested ones) in `source` and `target`, times the cost of a key-based
|
||||
object operation (looking up a key, or adding one to the patch): logarithmic for [`json`](../json.md), and linear for
|
||||
[`ordered_json`](../ordered_json.md) (see [`ordered_map` complexity](../ordered_map.md#complexity)). For
|
||||
`ordered_json`, diffing two objects with `n` and `m` members therefore takes O((n+m)²).
|
||||
|
||||
## Notes
|
||||
|
||||
- A merge patch uses `#!json null` to remove a member, so it cannot set a member to `#!json null`. As in RFC 7396, a
|
||||
`#!json null` member of `target` is treated as absent: the patch removes the member from `source` instead of setting
|
||||
it to `#!json null`. A member that is `#!json null` in both `source` and `target` is unchanged and left out of the
|
||||
patch, so `source` keeps it.
|
||||
- A merge patch cannot reorder object members, and [`merge_patch`](merge_patch.md) appends added members at the end.
|
||||
For [`ordered_json`](../ordered_json.md), the patched value therefore has the same members as `target`, but not
|
||||
necessarily in the same order, and may compare unequal to it. The patch lists changed and removed members in the
|
||||
order of `source`, followed by added members in the order of `target`.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The following code shows how a JSON Merge Patch is created as a diff for two JSON values.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/merge_diff.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/merge_diff.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396)
|
||||
- [merge_patch](merge_patch.md) applies a JSON Merge Patch
|
||||
- [diff](diff.md) creates a diff as a JSON Patch
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -67,6 +67,7 @@ returned by `#!cpp (*this)[key]`); it is read as it was when `merge_patch()` was
|
||||
|
||||
- [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396)
|
||||
- [patch](patch.md) apply a JSON patch
|
||||
- [merge_diff](merge_diff.md) creates a diff as a JSON Merge Patch
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ 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
|
||||
@@ -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.
|
||||
@@ -0,0 +1,51 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using namespace nlohmann::literals;
|
||||
|
||||
int main()
|
||||
{
|
||||
// the source document
|
||||
json source = R"(
|
||||
{
|
||||
"title": "Goodbye!",
|
||||
"author": {
|
||||
"givenName": "John",
|
||||
"familyName": "Doe"
|
||||
},
|
||||
"tags": [
|
||||
"example",
|
||||
"sample"
|
||||
],
|
||||
"content": "This will be unchanged"
|
||||
}
|
||||
)"_json;
|
||||
|
||||
// the target document
|
||||
json target = R"(
|
||||
{
|
||||
"title": "Hello!",
|
||||
"author": {
|
||||
"givenName": "John"
|
||||
},
|
||||
"tags": [
|
||||
"example"
|
||||
],
|
||||
"content": "This will be unchanged",
|
||||
"phoneNumber": "+01-123-456-7890"
|
||||
}
|
||||
)"_json;
|
||||
|
||||
// create the patch
|
||||
json patch = json::merge_diff(source, target);
|
||||
|
||||
// roundtrip
|
||||
json patched_source = source;
|
||||
patched_source.merge_patch(patch);
|
||||
|
||||
// output patch and roundtrip result
|
||||
std::cout << std::setw(4) << patch << "\n\n"
|
||||
<< std::setw(4) << patched_source << std::endl;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"author": {
|
||||
"familyName": null
|
||||
},
|
||||
"phoneNumber": "+01-123-456-7890",
|
||||
"tags": [
|
||||
"example"
|
||||
],
|
||||
"title": "Hello!"
|
||||
}
|
||||
|
||||
{
|
||||
"author": {
|
||||
"givenName": "John"
|
||||
},
|
||||
"content": "This will be unchanged",
|
||||
"phoneNumber": "+01-123-456-7890",
|
||||
"tags": [
|
||||
"example"
|
||||
],
|
||||
"title": "Hello!"
|
||||
}
|
||||
@@ -9,6 +9,9 @@ syntax that closely mimics the document being modified. Unlike [JSON Patch](json
|
||||
express every kind of change (e.g., it cannot reorder array elements or remove a specific array element), but it is
|
||||
easier to read and write for object-shaped documents.
|
||||
|
||||
The function [`merge_diff`](../api/basic_json/merge_diff.md) creates a merge patch that changes one JSON value into
|
||||
another.
|
||||
|
||||
!!! tip "Not a general deep merge"
|
||||
|
||||
A merge patch is not a general deep merge: a `#!json null` value in the patch deletes the key from the target.
|
||||
@@ -35,4 +38,5 @@ easier to read and write for object-shaped documents.
|
||||
- [JSON Patch and Diff](json_patch.md) - a more expressive alternative that describes a sequence of operations
|
||||
- [JSON Pointer](json_pointer.md) - the addressing scheme used by JSON Patch
|
||||
- Function [`merge_patch`](../api/basic_json/merge_patch.md)
|
||||
- Function [`merge_diff`](../api/basic_json/merge_diff.md)
|
||||
- Function [`update`](../api/basic_json/update.md) - merge objects, optionally recursively
|
||||
@@ -343,8 +343,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"
|
||||
|
||||
@@ -499,13 +499,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.
|
||||
@@ -517,6 +518,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 |
|
||||
@@ -525,6 +543,7 @@ therefore silently changes parse results rather than raising an error. See
|
||||
| `#!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 |
|
||||
| class types, enumerations | not usable; `#!cpp std::is_integral` must hold |
|
||||
| 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) |
|
||||
| `#!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 |
|
||||
|
||||
## `NumberFloatType`
|
||||
|
||||
@@ -185,9 +185,11 @@ The lexer detects two optional extensions at compile time. Only `iterator_input_
|
||||
random-access input of single-byte characters:
|
||||
|
||||
- `supports_seek`, `get_consumed_count()`, and `copy_consumed_range()` let the lexer reconstruct already consumed input
|
||||
for error messages instead of copying every character it reads.
|
||||
for error messages instead of copying every character it reads. With `get_read_count()` and
|
||||
`count_consumed_lines()`, the lexer also derives the read position from the adapter and counts lines only when an
|
||||
error needs them, instead of counting characters and lines on every read.
|
||||
- `supports_bulk_scan`, `bulk_data()`, `bulk_remaining()`, and `bulk_skip()` let the lexer scan strings directly in
|
||||
contiguous memory, several bytes at a time.
|
||||
contiguous memory, several bytes at a time, and skip the spaces that indent a line in 8-byte steps.
|
||||
|
||||
The function `input_adapter` picks the right adapter for the argument passed to `parse`, `accept`, `sax_parse`, or the
|
||||
`from_*` functions:
|
||||
|
||||
@@ -182,6 +182,7 @@ nav:
|
||||
- 'json_serializer': api/basic_json/json_serializer.md
|
||||
- 'max_size': api/basic_json/max_size.md
|
||||
- 'meta': api/basic_json/meta.md
|
||||
- 'merge_diff': api/basic_json/merge_diff.md
|
||||
- 'merge_patch': api/basic_json/merge_patch.md
|
||||
- 'number_float_t': api/basic_json/number_float_t.md
|
||||
- 'number_integer_t': api/basic_json/number_integer_t.md
|
||||
|
||||
@@ -42,6 +42,25 @@ inline int count_leading_zeros(std::uint64_t x) noexcept
|
||||
#endif
|
||||
}
|
||||
|
||||
/// number of trailing zero bits of x (x != 0)
|
||||
inline int count_trailing_zeros(std::uint64_t x) noexcept
|
||||
{
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return __builtin_ctzll(x);
|
||||
#else
|
||||
int n = 0;
|
||||
for (int shift = 32; shift != 0; shift >>= 1)
|
||||
{
|
||||
if ((x << (64 - shift)) == 0)
|
||||
{
|
||||
n += shift;
|
||||
x >>= shift;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// the 128-bit product of two 64-bit numbers
|
||||
struct uint128_parts
|
||||
{
|
||||
|
||||
@@ -80,7 +80,11 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
|
||||
{
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
|
||||
// never null: a basic_json whose number_unsigned_t is number_integer_t
|
||||
// stores no number_unsigned values (#2573)
|
||||
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
|
||||
JSON_ASSERT(ptr != nullptr);
|
||||
val = static_cast<ArithmeticType>(*ptr);
|
||||
break;
|
||||
}
|
||||
case value_t::number_integer:
|
||||
@@ -150,7 +154,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_flo
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val)
|
||||
inline void from_json(const BasicJsonType& j, number_unsigned_overload_t<BasicJsonType>& val)
|
||||
{
|
||||
get_arithmetic_value(j, val);
|
||||
}
|
||||
@@ -406,7 +410,11 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val)
|
||||
{
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
|
||||
// never null: a basic_json whose number_unsigned_t is number_integer_t
|
||||
// stores no number_unsigned values (#2573)
|
||||
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
|
||||
JSON_ASSERT(ptr != nullptr);
|
||||
val = static_cast<ArithmeticType>(*ptr);
|
||||
break;
|
||||
}
|
||||
case value_t::number_integer:
|
||||
|
||||
@@ -303,14 +303,14 @@ inline void to_json(BasicJsonType& j, FloatType val) noexcept
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename CompatibleNumberUnsignedType,
|
||||
enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0>
|
||||
enable_if_t<is_compatible_integer_type<number_unsigned_overload_t<BasicJsonType>, CompatibleNumberUnsignedType>::value, int> = 0>
|
||||
inline void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept
|
||||
{
|
||||
external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val));
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename CompatibleNumberIntegerType,
|
||||
enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0>
|
||||
enable_if_t<is_compatible_number_integer_type<BasicJsonType, CompatibleNumberIntegerType>::value, int> = 0>
|
||||
inline void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept
|
||||
{
|
||||
external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val));
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // min
|
||||
#include <algorithm> // count, find, min
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstring> // strlen
|
||||
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next
|
||||
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
|
||||
#include <streambuf> // streambuf
|
||||
#include <string> // string, char_traits
|
||||
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
||||
@@ -297,6 +297,7 @@ class iterator_input_adapter
|
||||
return result;
|
||||
}
|
||||
|
||||
++eof_reads;
|
||||
return char_traits<char_type>::eof();
|
||||
}
|
||||
|
||||
@@ -306,6 +307,26 @@ class iterator_input_adapter
|
||||
return static_cast<std::size_t>(std::distance(begin, current));
|
||||
}
|
||||
|
||||
// number of get_character() calls so far, including those that returned
|
||||
// EOF; the lexer derives its position from it (see lexer::lazy_position)
|
||||
std::size_t get_read_count() const
|
||||
{
|
||||
return get_consumed_count() + eof_reads;
|
||||
}
|
||||
|
||||
// number of newlines in the consumed range [0, last_index); @a line_start
|
||||
// is set to the index after the last of them (0 if there is none); only
|
||||
// valid when supports_seek
|
||||
std::size_t count_consumed_lines(std::size_t last_index, std::size_t& line_start) const
|
||||
{
|
||||
const auto to = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(last_index));
|
||||
const auto newline = char_traits<char_type>::to_char_type('\n');
|
||||
using reverse_it = std::reverse_iterator<IteratorType>;
|
||||
const auto last_newline = std::find(reverse_it(to), reverse_it(begin), newline);
|
||||
line_start = static_cast<std::size_t>(std::distance(begin, last_newline.base()));
|
||||
return static_cast<std::size_t>(std::count(begin, last_newline.base(), newline));
|
||||
}
|
||||
|
||||
// append the already-consumed characters in the half-open range
|
||||
// [first_index, last_index) to @a out; only valid when supports_seek
|
||||
template<typename ContainerType>
|
||||
@@ -422,6 +443,8 @@ class iterator_input_adapter
|
||||
IteratorType begin;
|
||||
IteratorType current;
|
||||
SentinelType end;
|
||||
/// number of get_character() calls that returned EOF
|
||||
std::size_t eof_reads = 0;
|
||||
|
||||
template<typename BaseInputAdapter, size_t T>
|
||||
friend struct wide_string_input_helper;
|
||||
|
||||
@@ -217,7 +217,7 @@ struct diagnostic_positions
|
||||
{
|
||||
// Lexer has read the first character of the container, so
|
||||
// subtract 1 from the position to get the correct start position.
|
||||
v.start_position = lexer->get_position() - 1;
|
||||
v.start_position = lexer->get_chars_read_total() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ struct diagnostic_positions
|
||||
if (lexer)
|
||||
{
|
||||
// Lexer's position is past the closing brace or bracket, so set that as the end position.
|
||||
v.end_position = lexer->get_position();
|
||||
v.end_position = lexer->get_chars_read_total();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ struct diagnostic_positions
|
||||
// Lexer has read past the current field value, so set the end position to the current position.
|
||||
// The start position will be set below based on the length of the string representation
|
||||
// of the value.
|
||||
v.end_position = lexer->get_position();
|
||||
v.end_position = lexer->get_chars_read_total();
|
||||
|
||||
switch (v.type())
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <utility> // move
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/detail/bit_ops.hpp>
|
||||
#include <nlohmann/detail/input/input_adapters.hpp>
|
||||
#include <nlohmann/detail/input/number_parse.hpp>
|
||||
#include <nlohmann/detail/input/position_t.hpp>
|
||||
@@ -186,6 +187,15 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
static constexpr bool lazy_token_string =
|
||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||
|
||||
/// whether the read position is derived from the input adapter on demand
|
||||
/// instead of being counted on every scanned character. Seekable adapters
|
||||
/// know how much input they consumed and can revisit it, so
|
||||
/// chars_read_total is the adapter's read count (minus a pending unget),
|
||||
/// and line and column are recomputed from the consumed input only when
|
||||
/// get_position() is called (errors). Counting per character kept two
|
||||
/// increments and a newline check in the hot path of every get().
|
||||
static constexpr bool lazy_position = lazy_token_string;
|
||||
|
||||
/// whether a simulated unget can be passed on to the input adapter, which
|
||||
/// then leaves the character in the input; see
|
||||
/// input_adapter_supports_lookahead
|
||||
@@ -195,7 +205,9 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
/// whether string scanning may bulk-consume runs of ordinary characters
|
||||
/// directly from a contiguous input buffer (SWAR fast path). This requires
|
||||
/// the token to be reconstructible lazily (lazy_token_string), so bypassing
|
||||
/// the per-character capture in get() cannot lose error diagnostics.
|
||||
/// the per-character capture in get() cannot lose error diagnostics. It
|
||||
/// also implies lazy_position, so the bulk paths need not advance the
|
||||
/// position counters.
|
||||
static constexpr bool bulk_scan =
|
||||
lazy_token_string
|
||||
&& input_adapter_supports_bulk_scan<InputAdapterType>(is_detected<detect_supports_bulk_scan, InputAdapterType> {});
|
||||
@@ -331,10 +343,6 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
}
|
||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||
ia.bulk_skip(pos);
|
||||
// the run contains no newline (all bytes < 0x20 are treated as special),
|
||||
// so only the flat character counters advance
|
||||
position.chars_read_total += pos;
|
||||
position.chars_read_current_line += pos;
|
||||
}
|
||||
|
||||
/// streaming input: no bulk fast path
|
||||
@@ -1567,8 +1575,6 @@ scan_number_done:
|
||||
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
||||
{
|
||||
ia.bulk_skip(len - 1);
|
||||
position.chars_read_total += (len - 1);
|
||||
position.chars_read_current_line += (len - 1);
|
||||
return integer_result;
|
||||
}
|
||||
// The value does not fit an integer, so this token converts as a
|
||||
@@ -1585,8 +1591,6 @@ scan_number_done:
|
||||
decimal_point_position = dot_index;
|
||||
|
||||
ia.bulk_skip(len - 1);
|
||||
position.chars_read_total += (len - 1);
|
||||
position.chars_read_current_line += (len - 1);
|
||||
|
||||
return convert_number(number_type, mantissa_end);
|
||||
}
|
||||
@@ -1637,7 +1641,7 @@ scan_number_done:
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// the first character of the token has already been read, hence the -1
|
||||
token_start_position = position.chars_read_total - 1;
|
||||
token_start_position = get_chars_read_total() - 1;
|
||||
#endif
|
||||
|
||||
note_token_start(std::integral_constant<bool, lazy_token_string> {});
|
||||
@@ -1690,6 +1694,15 @@ scan_number_done:
|
||||
/// per-character position counters (line-count-on-'\n' bookkeeping is
|
||||
/// handled afterwards, in track_after_read(), once `current` is known)
|
||||
void advance_position() noexcept
|
||||
{
|
||||
advance_position_impl(std::integral_constant<bool, lazy_position> {});
|
||||
}
|
||||
|
||||
/// seekable adapter: the position is derived from the adapter on demand
|
||||
void advance_position_impl(std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
/// streaming adapter: count the character
|
||||
void advance_position_impl(std::false_type /*lazy*/) noexcept
|
||||
{
|
||||
++position.chars_read_total;
|
||||
++position.chars_read_current_line;
|
||||
@@ -1703,7 +1716,16 @@ scan_number_done:
|
||||
// seekable adapters reconstruct the token lazily on error (see
|
||||
// get_token_string), so the eager per-character copy is skipped
|
||||
capture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||
track_newline(std::integral_constant<bool, lazy_position> {});
|
||||
return current;
|
||||
}
|
||||
|
||||
/// seekable adapter: lines are counted on demand in get_position()
|
||||
void track_newline(std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
/// streaming adapter: start a new line after reading a newline
|
||||
void track_newline(std::false_type /*lazy*/) noexcept
|
||||
{
|
||||
if (current == '\n')
|
||||
{
|
||||
++position.lines_read;
|
||||
@@ -1712,8 +1734,6 @@ scan_number_done:
|
||||
chars_read_before_newline = position.chars_read_current_line;
|
||||
position.chars_read_current_line = 0;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1757,12 +1777,22 @@ scan_number_done:
|
||||
We implement unget by setting variable next_unget to true. The input is not
|
||||
changed - we just simulate ungetting by modifying chars_read_total,
|
||||
chars_read_current_line, and token_string. The next call to get() will
|
||||
behave as if the unget character is read again.
|
||||
behave as if the unget character is read again. With lazy_position, the
|
||||
pending next_unget is all get_chars_read_total() needs.
|
||||
*/
|
||||
void unget()
|
||||
{
|
||||
next_unget = true;
|
||||
unget_position(std::integral_constant<bool, lazy_position> {});
|
||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||
}
|
||||
|
||||
/// seekable adapter: get_chars_read_total() accounts for next_unget
|
||||
void unget_position(std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
/// streaming adapter: step the counters back over the ungotten character
|
||||
void unget_position(std::false_type /*lazy*/) noexcept
|
||||
{
|
||||
--position.chars_read_total;
|
||||
|
||||
// in case we "unget" a newline, we have to also decrement the lines_read
|
||||
@@ -1785,8 +1815,6 @@ scan_number_done:
|
||||
{
|
||||
--position.chars_read_current_line;
|
||||
}
|
||||
|
||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||
}
|
||||
|
||||
/// adapter without lookahead: nothing to do (see release_lookahead)
|
||||
@@ -1857,10 +1885,21 @@ scan_number_done:
|
||||
// diagnostics
|
||||
/////////////////////
|
||||
|
||||
/// return position of last read token
|
||||
constexpr position_t get_position() const noexcept
|
||||
/*!
|
||||
@brief return the number of characters read so far
|
||||
|
||||
Unlike get_position(), this is O(1) for all adapters, so it may be called
|
||||
for every value (JSON_DIAGNOSTIC_POSITIONS).
|
||||
*/
|
||||
std::size_t get_chars_read_total() const noexcept
|
||||
{
|
||||
return position;
|
||||
return get_chars_read_total_impl(std::integral_constant<bool, lazy_position> {});
|
||||
}
|
||||
|
||||
/// return position of last read token
|
||||
position_t get_position() const
|
||||
{
|
||||
return get_position_impl(std::integral_constant<bool, lazy_position> {});
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1897,6 +1936,43 @@ scan_number_done:
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
/// seekable adapter: every get() either read from the adapter (including
|
||||
/// reads at EOF, which get() counts as characters as well) or replayed an
|
||||
/// ungotten character, so the count is the adapter's read count minus a
|
||||
/// still pending unget
|
||||
std::size_t get_chars_read_total_impl(std::true_type /*lazy*/) const noexcept
|
||||
{
|
||||
return ia.get_read_count() - (next_unget ? 1u : 0u);
|
||||
}
|
||||
|
||||
/// streaming adapter: the counter is maintained by get() and unget()
|
||||
std::size_t get_chars_read_total_impl(std::false_type /*lazy*/) const noexcept
|
||||
{
|
||||
return position.chars_read_total;
|
||||
}
|
||||
|
||||
/// seekable adapter: count the lines in the consumed input; O(input read
|
||||
/// so far), but only called on errors
|
||||
position_t get_position_impl(std::true_type /*lazy*/) const
|
||||
{
|
||||
position_t result;
|
||||
result.chars_read_total = get_chars_read_total();
|
||||
// reads at EOF count as characters but are not part of the input
|
||||
const std::size_t consumed = (std::min)(result.chars_read_total, ia.get_consumed_count());
|
||||
std::size_t line_start = 0;
|
||||
result.lines_read = ia.count_consumed_lines(consumed, line_start);
|
||||
result.chars_read_current_line = result.chars_read_total - line_start;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// streaming adapter: the position is maintained by get() and unget()
|
||||
position_t get_position_impl(std::false_type /*lazy*/) const noexcept
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
public:
|
||||
/// seekable adapter: rebuild the last read token from the input on demand
|
||||
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
|
||||
{
|
||||
@@ -2004,15 +2080,57 @@ scan_number_done:
|
||||
// into one that reloads the pointer from memory every character
|
||||
do
|
||||
{
|
||||
if (current == '\n')
|
||||
{
|
||||
skip_indentation(std::integral_constant<bool, bulk_scan> {});
|
||||
}
|
||||
get_ignoring_pending_unget();
|
||||
}
|
||||
while (current_is_whitespace());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief contiguous input: skip the space indentation that follows a newline
|
||||
8 bytes at a time
|
||||
|
||||
Pretty-printed JSON indents every line with a run of spaces, which the loop
|
||||
in skip_whitespace() would otherwise read one character at a time. The
|
||||
spaces are skipped 8 bytes at a time; the last 7 bytes of the input and any
|
||||
other whitespace are left to that loop. Skipping bypasses get(), which is
|
||||
fine because bulk_scan implies lazy_position: the position is derived from
|
||||
the adapter, not counted per character.
|
||||
*/
|
||||
void skip_indentation(std::true_type /*bulk*/)
|
||||
{
|
||||
JSON_ASSERT(!next_unget);
|
||||
const std::size_t remaining = ia.bulk_remaining();
|
||||
if (remaining < 8)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const char* const data = reinterpret_cast<const char*>(ia.bulk_data());
|
||||
std::size_t pos = 0;
|
||||
for (; pos + 8 <= remaining; pos += 8)
|
||||
{
|
||||
// little-endian, so the first byte is the lowest
|
||||
const std::uint64_t non_space = read_eight_bytes(data + pos) ^ 0x2020202020202020ull;
|
||||
if (non_space != 0)
|
||||
{
|
||||
// skip the spaces before the first other byte of this word
|
||||
pos += static_cast<std::size_t>(count_trailing_zeros(non_space)) / 8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ia.bulk_skip(pos);
|
||||
}
|
||||
|
||||
/// streaming input: no bulk fast path
|
||||
void skip_indentation(std::false_type /*bulk*/) const noexcept {}
|
||||
|
||||
token_type scan()
|
||||
{
|
||||
// initially, skip the BOM
|
||||
if (position.chars_read_total == 0 && !skip_bom())
|
||||
if (get_chars_read_total() == 0 && !skip_bom())
|
||||
{
|
||||
error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given";
|
||||
return token_type::parse_error;
|
||||
@@ -2039,7 +2157,7 @@ scan_number_done:
|
||||
token_type scan_expecting(token_type expected_type)
|
||||
{
|
||||
JSON_ASSERT(expected_type == token_type::name_separator || expected_type == token_type::value_separator);
|
||||
JSON_ASSERT(position.chars_read_total > 0);
|
||||
JSON_ASSERT(get_chars_read_total() > 0);
|
||||
const char_int_type expected_char = static_cast<unsigned char>((expected_type == token_type::name_separator) ? ':' : ',');
|
||||
skip_whitespace();
|
||||
if (JSON_HEDLEY_LIKELY(current == expected_char))
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <cstring> // memcpy
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <type_traits> // is_signed, true_type, false_type
|
||||
|
||||
#include <nlohmann/detail/bit_ops.hpp>
|
||||
#include <nlohmann/detail/input/pow5_table.hpp>
|
||||
@@ -44,6 +45,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
|
||||
|
||||
@@ -77,8 +92,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>());
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -370,7 +370,7 @@ class parser
|
||||
}
|
||||
case token_type::end_of_input:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1))
|
||||
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
|
||||
{
|
||||
return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON");
|
||||
}
|
||||
@@ -520,8 +520,10 @@ class parser
|
||||
{
|
||||
// MSVC 2015 reports C4100 (unreferenced parameter) when SAX::parse_error is static
|
||||
static_cast<void>(sax);
|
||||
return sax.parse_error(m_lexer.get_position(), m_lexer.get_token_string(),
|
||||
parse_error::create(exception_id::syntax_error, m_lexer.get_position(), message, nullptr));
|
||||
// computing the position is O(input read so far) for seekable input
|
||||
const position_t position = m_lexer.get_position();
|
||||
return sax.parse_error(position, m_lexer.get_token_string(),
|
||||
parse_error::create(exception_id::syntax_error, position, message, nullptr));
|
||||
}
|
||||
|
||||
std::string exception_message(const token_type expected, const std::string& context)
|
||||
|
||||
@@ -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 < std::is_same<typename BasicJsonType::number_integer_t, typename BasicJsonType::number_unsigned_t>::value&&
|
||||
std::is_integral<CompatibleNumberIntegerType>::value&&
|
||||
std::is_unsigned<CompatibleNumberIntegerType>::value&&
|
||||
!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,27 @@ constexpr bool value_in_range_of(T val)
|
||||
template<bool Value>
|
||||
using bool_constant = std::integral_constant<bool, Value>;
|
||||
|
||||
// Stands in for number_unsigned_t in overload sets if it is the same type as
|
||||
// number_integer_t, so that the unsigned overloads do not collide with the
|
||||
// integer ones. Never constructed. Such a basic_json stores every integer as
|
||||
// number_integer (#2573).
|
||||
struct same_as_number_integer_type {};
|
||||
|
||||
// NumberUnsignedType, or same_as_number_integer_type if it is the same type as
|
||||
// NumberIntegerType; basic_json uses this one, as it cannot name its own member
|
||||
// types through an incomplete basic_json (MSVC reports a recursive alias, and
|
||||
// old GCC versions crash)
|
||||
template<typename NumberIntegerType, typename NumberUnsignedType>
|
||||
using number_unsigned_overload_type = typename std::conditional <
|
||||
std::is_same<NumberIntegerType, NumberUnsignedType>::value,
|
||||
same_as_number_integer_type,
|
||||
NumberUnsignedType >::type;
|
||||
|
||||
// number_unsigned_t of BasicJsonType, or same_as_number_integer_type if it is
|
||||
// the same type as number_integer_t
|
||||
template<typename BasicJsonType>
|
||||
using number_unsigned_overload_t = number_unsigned_overload_type<typename BasicJsonType::number_integer_t, typename BasicJsonType::number_unsigned_t>;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// is_c_string
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -221,7 +221,7 @@ class binary_writer
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
write_cbor_head(0x00, j.m_data.m_value.number_unsigned);
|
||||
write_cbor_head(0x00, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1601,7 +1601,7 @@ class binary_writer
|
||||
return calc_bson_integer_size(j.m_data.m_value.number_integer);
|
||||
|
||||
case value_t::number_unsigned:
|
||||
return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);
|
||||
return calc_bson_unsigned_size(conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
|
||||
case value_t::string:
|
||||
return calc_bson_string_size(*j.m_data.m_value.string, j);
|
||||
@@ -1643,7 +1643,7 @@ class binary_writer
|
||||
return write_bson_integer(name, j.m_data.m_value.number_integer);
|
||||
|
||||
case value_t::number_unsigned:
|
||||
return write_bson_unsigned(name, j.m_data.m_value.number_unsigned);
|
||||
return write_bson_unsigned(name, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
|
||||
case value_t::string:
|
||||
return write_bson_string(name, *j.m_data.m_value.string);
|
||||
|
||||
@@ -49,6 +49,12 @@ class serializer
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
/// type of the absolute value of an integer: number_unsigned_t, or its
|
||||
/// unsigned counterpart if it is signed (it may be number_integer_t, #2573)
|
||||
using number_magnitude_t = typename std::conditional <
|
||||
std::is_integral<number_unsigned_t>::value && std::is_signed<number_unsigned_t>::value,
|
||||
std::make_unsigned<number_unsigned_t>,
|
||||
std::enable_if<true, number_unsigned_t >>::type::type;
|
||||
using binary_char_t = typename BasicJsonType::binary_t::value_type;
|
||||
|
||||
public:
|
||||
@@ -1227,7 +1233,7 @@ class serializer
|
||||
@param[in] x unsigned integer number to count its digits
|
||||
@return number of decimal digits
|
||||
*/
|
||||
unsigned int count_digits(number_unsigned_t x) noexcept
|
||||
unsigned int count_digits(number_magnitude_t x) noexcept
|
||||
{
|
||||
unsigned int n_digits = 1;
|
||||
for (;;)
|
||||
@@ -1405,7 +1411,7 @@ class serializer
|
||||
// use a pointer to fill the buffer
|
||||
auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto)
|
||||
|
||||
number_unsigned_t abs_value;
|
||||
number_magnitude_t abs_value;
|
||||
|
||||
unsigned int n_chars{};
|
||||
|
||||
@@ -1419,7 +1425,7 @@ class serializer
|
||||
}
|
||||
else
|
||||
{
|
||||
abs_value = static_cast<number_unsigned_t>(x);
|
||||
abs_value = static_cast<number_magnitude_t>(x);
|
||||
n_chars = count_digits(abs_value);
|
||||
}
|
||||
|
||||
@@ -1562,17 +1568,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
|
||||
*
|
||||
@@ -1582,10 +1577,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:
|
||||
|
||||
+205
-14
@@ -42,9 +42,14 @@
|
||||
// each of these, not only the forward declarations from json_fwd.hpp, so IWYU's suggestion to
|
||||
// drop them (nothing in this file otherwise names the type) would break every downstream
|
||||
// translation unit that relies on basic_json<>'s defaults actually being usable.
|
||||
#include <nlohmann/adl_serializer.hpp> // IWYU pragma: keep
|
||||
// export: client code names what these headers declare (nlohmann::json and ordered_json from
|
||||
// json_fwd.hpp, adl_serializer for specializations, json_ref behind brace initialization, the
|
||||
// NLOHMANN_DEFINE_* and NLOHMANN_JSON_NAMESPACE_* macros, and the literals at the end of this file), and include analyzers must
|
||||
// attribute it to this header, not tell clients to include the internal ones (issue #5812).
|
||||
// tests/iwyu_consumer checks this.
|
||||
#include <nlohmann/adl_serializer.hpp> // IWYU pragma: export
|
||||
#include <nlohmann/byte_container_with_subtype.hpp>
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/abi_macros.hpp> // IWYU pragma: export
|
||||
#include <nlohmann/detail/conversions/from_json.hpp> // IWYU pragma: keep
|
||||
#include <nlohmann/detail/conversions/to_json.hpp> // IWYU pragma: keep
|
||||
#include <nlohmann/detail/exceptions.hpp>
|
||||
@@ -61,8 +66,8 @@
|
||||
#include <nlohmann/detail/iterators/primitive_iterator.hpp>
|
||||
#include <nlohmann/detail/json_custom_base_class.hpp>
|
||||
#include <nlohmann/detail/json_pointer.hpp>
|
||||
#include <nlohmann/detail/json_ref.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/json_ref.hpp> // IWYU pragma: export
|
||||
#include <nlohmann/detail/macro_scope.hpp> // IWYU pragma: export
|
||||
#include <nlohmann/detail/meta/detected.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
#include <nlohmann/detail/string_escape.hpp>
|
||||
@@ -74,7 +79,7 @@
|
||||
#include <nlohmann/detail/output/serializer.hpp>
|
||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include <nlohmann/json_fwd.hpp> // IWYU pragma: export
|
||||
#include <nlohmann/ordered_map.hpp> // IWYU pragma: keep
|
||||
#include <nlohmann/thirdparty/hedley/hedley.hpp>
|
||||
|
||||
@@ -495,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
|
||||
@@ -570,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
|
||||
@@ -2176,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();
|
||||
}
|
||||
@@ -2257,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();
|
||||
@@ -2842,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;
|
||||
}
|
||||
@@ -7449,6 +7470,176 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
/// @brief creates a diff as a JSON Merge Patch
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/merge_diff/
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
static basic_json merge_diff(const basic_json& source, const basic_json& target)
|
||||
{
|
||||
// unless both are objects, the patch is the target itself
|
||||
if (!source.is_object() || !target.is_object())
|
||||
{
|
||||
return target;
|
||||
}
|
||||
|
||||
return merge_diff_recursively(source, target, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
/// @brief two objects @ref merge_diff_iteratively is diffing
|
||||
struct merge_diff_frame
|
||||
{
|
||||
merge_diff_frame(const basic_json* source_, const basic_json* target_)
|
||||
: source(source_), target(target_), member(source_->cbegin()), patch(value_t::object)
|
||||
{}
|
||||
|
||||
// declared for GCC's -Weffc++, as in diff_frame
|
||||
merge_diff_frame(const merge_diff_frame&) = default;
|
||||
merge_diff_frame(merge_diff_frame&&) = default;
|
||||
merge_diff_frame& operator=(const merge_diff_frame&) = default;
|
||||
merge_diff_frame& operator=(merge_diff_frame&&) = default;
|
||||
~merge_diff_frame() = default;
|
||||
|
||||
/// the objects being diffed
|
||||
const basic_json* source;
|
||||
const basic_json* target;
|
||||
/// the member of source being diffed; advanced once it is done
|
||||
const_iterator member;
|
||||
/// the patch for the members of source diffed so far
|
||||
basic_json patch;
|
||||
};
|
||||
|
||||
/// @brief add the members of object @a target that @a source does not
|
||||
/// have to the merge patch @a result
|
||||
static void merge_diff_added_members(basic_json& result, const basic_json& source, const basic_json& target)
|
||||
{
|
||||
for (auto it = target.begin(); it != target.end(); ++it)
|
||||
{
|
||||
auto itf = source.find(it.key());
|
||||
if (itf == source.end())
|
||||
{
|
||||
result[it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief @ref merge_diff of two objects at nesting level @a depth
|
||||
|
||||
Diffing two nested objects calls this function again, once per nesting
|
||||
level, so values nested deeply enough would exhaust the call stack and
|
||||
terminate the process. The descent is bounded here: once @ref
|
||||
detail::recursion_depth_limit levels have been entered, @ref
|
||||
merge_diff_iteratively diffs what is left without the call stack.
|
||||
*/
|
||||
static basic_json merge_diff_recursively(const basic_json& source, const basic_json& target, const std::size_t depth)
|
||||
{
|
||||
JSON_ASSERT(source.is_object() && target.is_object());
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||
{
|
||||
return merge_diff_iteratively(source, target);
|
||||
}
|
||||
|
||||
basic_json result(value_t::object);
|
||||
|
||||
for (auto it = source.begin(); it != source.end(); ++it)
|
||||
{
|
||||
auto itf = target.find(it.key());
|
||||
if (itf != target.end())
|
||||
{
|
||||
if (it.value().is_object() && itf.value().is_object())
|
||||
{
|
||||
auto diff = merge_diff_recursively(it.value(), itf.value(), depth + 1);
|
||||
if (!diff.empty())
|
||||
{
|
||||
result[it.key()] = std::move(diff);
|
||||
}
|
||||
}
|
||||
else if (it.value() != itf.value())
|
||||
{
|
||||
result[it.key()] = itf.value();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result[it.key()] = value_t::null;
|
||||
}
|
||||
}
|
||||
|
||||
merge_diff_added_members(result, source, target);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief @ref merge_diff of two objects without the call stack
|
||||
|
||||
Produces the same patch as @ref merge_diff_recursively, keeping the objects
|
||||
being diffed on an explicit stack instead of descending into them. Two
|
||||
nested objects are still diffed completely before the next member, so the
|
||||
patch has its members in the same order as in the recursive version. Only
|
||||
reached for objects nested more deeply than @ref
|
||||
detail::recursion_depth_limit.
|
||||
*/
|
||||
static basic_json merge_diff_iteratively(const basic_json& source, const basic_json& target)
|
||||
{
|
||||
// the patch of source and target, set once their frame is popped
|
||||
basic_json result;
|
||||
std::vector<merge_diff_frame> stack;
|
||||
stack.emplace_back(&source, &target);
|
||||
while (!stack.empty())
|
||||
{
|
||||
// the frame's members are copied out and it is changed through
|
||||
// stack.back(): pushing a frame for nested objects or popping
|
||||
// this one would invalidate a reference to it
|
||||
const basic_json* const s = stack.back().source;
|
||||
const basic_json* const t = stack.back().target;
|
||||
const const_iterator it = stack.back().member;
|
||||
|
||||
if (it != s->cend())
|
||||
{
|
||||
const const_iterator itf = t->find(it.key());
|
||||
if (itf != t->cend())
|
||||
{
|
||||
if (it.value().is_object() && itf.value().is_object())
|
||||
{
|
||||
// member is advanced once their patch is under its key
|
||||
stack.emplace_back(&it.value(), &itf.value());
|
||||
continue;
|
||||
}
|
||||
if (it.value() != itf.value())
|
||||
{
|
||||
stack.back().patch[it.key()] = itf.value();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.back().patch[it.key()] = value_t::null;
|
||||
}
|
||||
++stack.back().member;
|
||||
continue;
|
||||
}
|
||||
|
||||
merge_diff_added_members(stack.back().patch, *s, *t);
|
||||
|
||||
// these objects are done: continue with the ones they are in
|
||||
basic_json patch = std::move(stack.back().patch);
|
||||
stack.pop_back();
|
||||
if (stack.empty())
|
||||
{
|
||||
result = std::move(patch);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!patch.empty())
|
||||
{
|
||||
stack.back().patch[stack.back().member.key()] = std::move(patch);
|
||||
}
|
||||
++stack.back().member;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
@@ -7625,7 +7816,7 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
|
||||
// where needed.
|
||||
#ifndef JSON_NO_AUTOMATIC_UDLS
|
||||
// NOLINTNEXTLINE(misc-header-include-cycle): json_literals.hpp includes this header
|
||||
#include <nlohmann/json_literals.hpp>
|
||||
#include <nlohmann/json_literals.hpp> // IWYU pragma: export
|
||||
#endif
|
||||
|
||||
#endif // INCLUDE_NLOHMANN_JSON_HPP_
|
||||
Generated
+702
-122
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,62 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
// See values.cpp: the conversion macros and nlohmann::adl_serializer must be available through
|
||||
// <nlohmann/json.hpp> alone. NLOHMANN_JSON_SERIALIZE_ENUM (from the same header as
|
||||
// NLOHMANN_DEFINE_TYPE_INTRUSIVE) is left out: IWYU attributes the basic_json comparison in its
|
||||
// expansion to an unrelated standard header, the ambiguity described for json.hpp in cmake/ci.cmake.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace ns
|
||||
{
|
||||
struct person
|
||||
{
|
||||
std::string name;
|
||||
int age;
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE(person, name, age)
|
||||
};
|
||||
|
||||
struct legacy
|
||||
{
|
||||
int value;
|
||||
};
|
||||
} // namespace ns
|
||||
|
||||
namespace nlohmann
|
||||
{
|
||||
template<>
|
||||
struct adl_serializer<ns::legacy>
|
||||
{
|
||||
static void to_json(json& j, const ns::legacy& l)
|
||||
{
|
||||
j = l.value;
|
||||
}
|
||||
|
||||
static void from_json(const json& j, ns::legacy& l)
|
||||
{
|
||||
l.value = j.get<int>();
|
||||
}
|
||||
};
|
||||
} // namespace nlohmann
|
||||
|
||||
int main()
|
||||
{
|
||||
ns::person ada;
|
||||
ada.name = "Ada";
|
||||
ada.age = 36;
|
||||
ns::legacy one;
|
||||
one.value = 1;
|
||||
|
||||
const nlohmann::json j = ada;
|
||||
const auto p = j.get<ns::person>();
|
||||
const auto l = nlohmann::json(one).get<ns::legacy>();
|
||||
return p.age - 36 + l.value - 1;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
// See values.cpp: the user-defined literals must be available through <nlohmann/json.hpp> alone.
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
|
||||
int main()
|
||||
{
|
||||
const auto j = R"({"a": [1, 2]})"_json;
|
||||
return j.at("/a/1"_json_pointer).get<int>() - 2;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
// Uses the library the way a client does, including only <nlohmann/json.hpp>. ci_single_binaries
|
||||
// runs include-what-you-use and clang-tidy's misc-include-cleaner on this file and fails if either
|
||||
// asks for another nlohmann header, which would mean json.hpp does not export it (issue #5812).
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
// brace initialization (nlohmann::json, detail::json_ref)
|
||||
nlohmann::json j = {{"pi", 3.141}, {"list", {1, 0, 2}}};
|
||||
const nlohmann::ordered_json o = {{"b", 1}, {"a", 2}};
|
||||
|
||||
j["parsed"] = nlohmann::json::parse(R"({"happy": true})");
|
||||
j["binary"] = nlohmann::json::binary(std::vector<std::uint8_t> {0xCA, 0xFE}, 42);
|
||||
|
||||
const std::string s = j.dump() + o.dump();
|
||||
return s.empty() ? 1 : 0;
|
||||
}
|
||||
@@ -938,6 +938,10 @@ TEST_CASE("Eisel-Lemire float conversion")
|
||||
const int k = i % 64;
|
||||
const std::uint64_t x = (std::uint64_t{1} << k) | (a & ((std::uint64_t{1} << k) - 1));
|
||||
CHECK(nlohmann::detail::count_leading_zeros(x) == 63 - k);
|
||||
|
||||
// the mirror image: bit k set, arbitrary bits above it
|
||||
const std::uint64_t y = (std::uint64_t{1} << k) | (k == 63 ? 0 : (a << (k + 1)));
|
||||
CHECK(nlohmann::detail::count_trailing_zeros(y) == k);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,13 @@
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
// capture whether JSON_DISABLE_ENUM_SERIALIZATION was enabled on the command
|
||||
// line before including json.hpp, which #undefs it; std::byte is an enum, so
|
||||
// converting std::vector<std::byte> needs enum serialization
|
||||
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
|
||||
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||
#endif
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -85,3 +92,53 @@ TEST_CASE("binary type whose value type is not std::uint8_t")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
// moved from unit-regression3.cpp, so that file does not instantiate a second
|
||||
// basic_json specialization (MinGW section limit, see windows.yml)
|
||||
TEST_CASE("std::vector<std::byte> as binary type")
|
||||
{
|
||||
SECTION("issue #4804: from_cbor incompatible with std::vector<std::byte> as binary_t")
|
||||
{
|
||||
const std::vector<std::uint8_t> data = {0x80};
|
||||
const auto decoded = byte_binary_json::from_cbor(data);
|
||||
CHECK((decoded == byte_binary_json::array()));
|
||||
}
|
||||
|
||||
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||
SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping")
|
||||
{
|
||||
// Test that assigning a custom BinaryType directly creates a binary value, not an array
|
||||
const std::vector<std::byte> original{std::byte{1}, std::byte{2}, std::byte{3}};
|
||||
const byte_binary_json j = original;
|
||||
CHECK(j.is_binary());
|
||||
CHECK(!j.is_array());
|
||||
|
||||
// Test round-tripping: extracting the binary value back as the custom container type
|
||||
const auto extracted = j.get<std::vector<std::byte>>();
|
||||
CHECK(extracted == original);
|
||||
|
||||
// Test that the default json alias behavior is unchanged: std::vector<uint8_t> -> array
|
||||
const nlohmann::json default_json = std::vector<std::uint8_t> {1, 2, 3};
|
||||
CHECK(default_json.is_array());
|
||||
CHECK(!default_json.is_binary());
|
||||
}
|
||||
|
||||
SECTION("discussion #4209 - custom BinaryType extraction from parsed array")
|
||||
{
|
||||
// Test that extracting a custom BinaryType from a parsed JSON array still works
|
||||
// (not just from a binary-typed node)
|
||||
const auto j = byte_binary_json::parse("[1,2,3]");
|
||||
CHECK(j.is_array());
|
||||
CHECK(!j.is_binary());
|
||||
|
||||
// Extracting as custom BinaryType should work from arrays
|
||||
const auto extracted = j.get<std::vector<std::byte>>();
|
||||
CHECK(extracted.size() == 3);
|
||||
CHECK(extracted[0] == std::byte{1});
|
||||
CHECK(extracted[1] == std::byte{2});
|
||||
CHECK(extracted[2] == std::byte{3});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
@@ -35,6 +35,7 @@ using nlohmann::json;
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <valarray>
|
||||
#include <vector>
|
||||
|
||||
#include "sax_event_loggers.hpp"
|
||||
|
||||
@@ -1250,3 +1251,139 @@ TEST_CASE_TEMPLATE("deserialization of different character types (UTF-32)", T, c
|
||||
CHECK(json::sax_parse(v, &l));
|
||||
CHECK(l.events.size() == 1);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// records the position and message of a parse error; reporting the error via
|
||||
// SAX does not throw, so the test also runs with JSON_NOEXCEPTION
|
||||
struct SaxErrorRecorder : public SaxEventLogger
|
||||
{
|
||||
bool parse_error(std::size_t position, const std::string& /*last_token*/, const json::exception& ex) override
|
||||
{
|
||||
error = std::to_string(position) + " " + ex.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string error {}; // NOLINT(readability-redundant-member-init)
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("parse error positions are the same for contiguous and stream input")
|
||||
{
|
||||
// contiguous input derives the position from the input on error, stream
|
||||
// input counts it while reading (see lexer::lazy_position); the cases
|
||||
// cover newlines before the error, ungetting a newline after a number,
|
||||
// newlines inside a string, a comment, and reads at the end of input
|
||||
struct test_case
|
||||
{
|
||||
std::string input;
|
||||
std::size_t byte;
|
||||
std::string message;
|
||||
};
|
||||
const std::vector<test_case> cases =
|
||||
{
|
||||
{"[\n 1,\n 2\n", 12, "[json.exception.parse_error.101] parse error at line 4, column 1: syntax error while parsing array - unexpected end of input; expected ']'"},
|
||||
{"[1\n,2,\n]", 8, "[json.exception.parse_error.101] parse error at line 3, column 1: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal"},
|
||||
{"[1\n x]", 5, "[json.exception.parse_error.101] parse error at line 2, column 2: syntax error while parsing array - invalid literal; last read: '1<U+000A> x'; expected ']'"},
|
||||
{"1\n2", 3, "[json.exception.parse_error.101] parse error at line 2, column 1: syntax error while parsing value - unexpected number literal; expected end of input"},
|
||||
{
|
||||
"{\"a\":\n\"b\nc\"}", 9, R"([json.exception.parse_error.101] parse error at line 3, column 0: syntax error while parsing value - invalid string: control character U+000A (LF) must be escaped to \u000A or \n; last read: '"b<U+000A>')"
|
||||
},
|
||||
{"/* a\n b */\n x", 13, "[json.exception.parse_error.101] parse error at line 3, column 2: syntax error while parsing value - invalid literal; last read: '/* a<U+000A> b */<U+000A> x'"},
|
||||
{"\n\n", 3, "[json.exception.parse_error.101] parse error at line 3, column 1: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal"},
|
||||
{"[\"a\",\n 1e\n]", 10, "[json.exception.parse_error.101] parse error at line 3, column 0: syntax error while parsing value - invalid number; expected '+', '-', or digit after exponent; last read: '1e<U+000A>'"},
|
||||
{"[tru\n]", 5, "[json.exception.parse_error.101] parse error at line 2, column 0: syntax error while parsing value - invalid literal; last read: '[tru<U+000A>'"},
|
||||
};
|
||||
|
||||
for (const auto& c : cases)
|
||||
{
|
||||
CAPTURE(c.input)
|
||||
const std::string expected = std::to_string(c.byte) + " " + c.message;
|
||||
|
||||
SaxErrorRecorder string_sax;
|
||||
CHECK_FALSE(json::sax_parse(c.input, &string_sax, json::input_format_t::json, true, true));
|
||||
CHECK(string_sax.error == expected);
|
||||
|
||||
SaxErrorRecorder pointer_sax;
|
||||
CHECK_FALSE(json::sax_parse(c.input.data(), c.input.data() + c.input.size(), &pointer_sax, json::input_format_t::json, true, true));
|
||||
CHECK(pointer_sax.error == expected);
|
||||
|
||||
std::istringstream is(c.input);
|
||||
SaxErrorRecorder stream_sax;
|
||||
CHECK_FALSE(json::sax_parse(is, &stream_sax, json::input_format_t::json, true, true));
|
||||
CHECK(stream_sax.error == expected);
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
TEST_CASE("indentation runs of every length around the 8-byte skip")
|
||||
{
|
||||
// contiguous input skips the spaces after a newline 8 bytes at a time (see
|
||||
// lexer::skip_indentation), stream input reads them one by one; results,
|
||||
// byte offsets, and messages must be the same for runs that end anywhere
|
||||
// relative to a word boundary, including at the end of the input
|
||||
const auto parse_result = [](const std::string & input, bool contiguous)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (contiguous)
|
||||
{
|
||||
return json::parse(input.data(), input.data() + input.size(), nullptr, true, true).dump();
|
||||
}
|
||||
std::istringstream is(input);
|
||||
return json::parse(is, nullptr, true, true).dump();
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
return std::to_string(e.byte).append(" ").append(e.what());
|
||||
}
|
||||
};
|
||||
|
||||
// replace every '~' in a pattern by an indentation run
|
||||
const auto indented = [](const std::string & pattern, const std::string & indent)
|
||||
{
|
||||
std::string result;
|
||||
for (const char c : pattern)
|
||||
{
|
||||
if (c == '~')
|
||||
{
|
||||
result += indent;
|
||||
}
|
||||
else
|
||||
{
|
||||
result += c;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const std::vector<std::string> patterns =
|
||||
{
|
||||
"[\n~1,\n~2\n]",
|
||||
"[\n~1,\n~x]",
|
||||
"[\n~1,\n~",
|
||||
"1\n~",
|
||||
"[\n~\t1,\n~\r\n~2]",
|
||||
"[\n~/* c */ 1]",
|
||||
"[\n~1\n~2]",
|
||||
};
|
||||
|
||||
for (std::size_t n = 0; n <= 33; ++n)
|
||||
{
|
||||
const std::string indent(n, ' ');
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
const std::string input = indented(pattern, indent);
|
||||
CAPTURE(n)
|
||||
CAPTURE(input)
|
||||
const std::string expected = parse_result(input, false);
|
||||
CHECK(parse_result(input, true) == expected);
|
||||
CHECK(json::accept(input.data(), input.data() + input.size(), true) == json::accept(input, true));
|
||||
}
|
||||
}
|
||||
|
||||
// and against fixed expectations, in case both paths broke the same way
|
||||
CHECK(parse_result(indented("[\n~x]", std::string(16, ' ')), true) == "19 [json.exception.parse_error.101] parse error at line 2, column 17: syntax error while parsing value - invalid literal; last read: '[<U+000A> x'");
|
||||
CHECK(parse_result(indented("[\n~1]", std::string(17, ' ')), true) == "[1]");
|
||||
}
|
||||
#endif
|
||||
@@ -567,9 +567,9 @@ TEST_CASE("Regression tests for extended diagnostics")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Better diagnostics past the descent bound of update() and merge_patch()")
|
||||
TEST_CASE("Better diagnostics past the descent bound of update(), merge_patch() and merge_diff()")
|
||||
{
|
||||
// Both merge objects nested more than detail::recursion_depth_limit()
|
||||
// All three handle objects nested more than detail::recursion_depth_limit()
|
||||
// (128) levels deep without recursing; the values they add or replace
|
||||
// there must still know their parents.
|
||||
// The values are built rather than parsed, so that the expected messages
|
||||
@@ -615,4 +615,19 @@ TEST_CASE("Better diagnostics past the descent bound of update() and merge_patch
|
||||
CHECK_THROWS_WITH_AS(p->at("x").at(0), expected_x.c_str(), json::type_error);
|
||||
CHECK_THROWS_WITH_AS(p->at("y").at(0), expected_y.c_str(), json::type_error);
|
||||
}
|
||||
|
||||
SECTION("merge_diff()")
|
||||
{
|
||||
// target and patch differ only in their innermost members, so the
|
||||
// diff of the two is nested as deeply: {"a": ... {"x": null, "y": 2}}
|
||||
const json diff = json::merge_diff(target, patch); // NOLINT(readability-suspicious-call-argument)
|
||||
|
||||
const json* p = &diff;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
p = &p->at("a");
|
||||
}
|
||||
CHECK(p->at("x").is_null());
|
||||
CHECK_THROWS_WITH_AS(p->at("y").at(0), expected_y.c_str(), json::type_error);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -58,11 +58,6 @@ using ordered_json = nlohmann::ordered_json;
|
||||
#include <optional>
|
||||
#elif __has_include(<experimental/optional>)
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// for #4804
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>;
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_20
|
||||
@@ -458,49 +453,6 @@ TEST_CASE("regression tests 3")
|
||||
#endif
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
SECTION("issue #4804: from_cbor incompatible with std::vector<std::byte> as binary_t")
|
||||
{
|
||||
const std::vector<std::uint8_t> data = {0x80};
|
||||
const auto decoded = json_4804::from_cbor(data);
|
||||
CHECK((decoded == json_4804::array()));
|
||||
}
|
||||
|
||||
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||
SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping")
|
||||
{
|
||||
// Test that assigning a custom BinaryType directly creates a binary value, not an array
|
||||
const std::vector<std::byte> original{std::byte{1}, std::byte{2}, std::byte{3}};
|
||||
const json_4804 j = original;
|
||||
CHECK(j.is_binary());
|
||||
CHECK(!j.is_array());
|
||||
|
||||
// Test round-tripping: extracting the binary value back as the custom container type
|
||||
const auto extracted = j.get<std::vector<std::byte>>();
|
||||
CHECK(extracted == original);
|
||||
|
||||
// Test that the default json alias behavior is unchanged: std::vector<uint8_t> -> array
|
||||
const json default_json = std::vector<std::uint8_t> {1, 2, 3};
|
||||
CHECK(default_json.is_array());
|
||||
CHECK(!default_json.is_binary());
|
||||
}
|
||||
|
||||
SECTION("discussion #4209 - custom BinaryType extraction from parsed array")
|
||||
{
|
||||
// Test that extracting a custom BinaryType from a parsed JSON array still works
|
||||
// (not just from a binary-typed node)
|
||||
const auto j = json_4804::parse("[1,2,3]");
|
||||
CHECK(j.is_array());
|
||||
CHECK(!j.is_binary());
|
||||
|
||||
// Extracting as custom BinaryType should work from arrays
|
||||
const auto extracted = j.get<std::vector<std::byte>>();
|
||||
CHECK(extracted.size() == 3);
|
||||
CHECK(extracted[0] == std::byte{1});
|
||||
CHECK(extracted[1] == std::byte{2});
|
||||
CHECK(extracted[2] == std::byte{3});
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("issue #5046 - implicit conversion of return json to std::optional no longer implicit")
|
||||
{
|
||||
const json jval{};
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// basic_json with the same type for number_integer_t and number_unsigned_t
|
||||
// (#2573): every integer is stored as number_integer
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template<typename IntegerType>
|
||||
using same_types_json = nlohmann::json::with_integers_t<IntegerType, IntegerType>;
|
||||
|
||||
using json64 = same_types_json<std::int64_t>;
|
||||
using json32 = same_types_json<std::int32_t>;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE_TEMPLATE("same type for number_integer_t and number_unsigned_t", Json, json64, json32)
|
||||
{
|
||||
using integer_t = typename Json::number_integer_t;
|
||||
using limits = std::numeric_limits<integer_t>;
|
||||
|
||||
SECTION("parsing stores integers as number_integer")
|
||||
{
|
||||
const Json j = Json::parse("[0, 1, -1, 2147483647, -2147483648]");
|
||||
for (const auto& element : j)
|
||||
{
|
||||
CHECK(element.is_number_integer());
|
||||
CHECK(!element.is_number_unsigned());
|
||||
CHECK(element.type() == nlohmann::detail::value_t::number_integer);
|
||||
}
|
||||
CHECK(j[1].template get<integer_t>() == 1);
|
||||
CHECK(j[2].template get<integer_t>() == -1);
|
||||
CHECK(j[3].template get<integer_t>() == 2147483647);
|
||||
CHECK(j[4].template get<std::int64_t>() == -2147483648LL);
|
||||
}
|
||||
|
||||
SECTION("integers above the maximum are parsed as floats")
|
||||
{
|
||||
// a positive literal is parsed as unsigned first; it must not wrap
|
||||
// around to a negative number_integer_t
|
||||
const auto above_max = std::to_string(static_cast<std::uint64_t>(limits::max()) + 1);
|
||||
const Json j1 = Json::parse(above_max);
|
||||
CHECK(j1.is_number_float());
|
||||
CHECK(j1.template get<double>() == static_cast<double>(static_cast<std::uint64_t>(limits::max()) + 1));
|
||||
|
||||
const Json j2 = Json::parse("18446744073709551615");
|
||||
CHECK(j2.is_number_float());
|
||||
CHECK(j2.template get<double>() == 18446744073709551615.0);
|
||||
|
||||
const Json j3 = Json::parse(std::to_string(limits::max()));
|
||||
CHECK(j3.is_number_integer());
|
||||
CHECK(j3.template get<integer_t>() == limits::max());
|
||||
}
|
||||
|
||||
SECTION("serialization of the extremes")
|
||||
{
|
||||
CHECK(Json(limits::max()).dump() == std::to_string(limits::max()));
|
||||
// the absolute value of the minimum does not fit into number_unsigned_t
|
||||
CHECK(Json(limits::min()).dump() == std::to_string(limits::min()));
|
||||
CHECK(Json::parse(std::to_string(limits::min())) == Json(limits::min()));
|
||||
CHECK(Json(0).dump() == "0");
|
||||
}
|
||||
|
||||
SECTION("construction from unsigned types")
|
||||
{
|
||||
const Json j1 = 5u;
|
||||
CHECK(j1.is_number_integer());
|
||||
CHECK(!j1.is_number_unsigned());
|
||||
CHECK(j1 == 5);
|
||||
|
||||
const Json j2 = static_cast<std::uint8_t>(200);
|
||||
CHECK(j2.is_number_integer());
|
||||
CHECK(j2.template get<integer_t>() == 200);
|
||||
|
||||
const Json j3 = std::vector<unsigned> {1, 2, 3};
|
||||
CHECK(j3.dump() == "[1,2,3]");
|
||||
CHECK(j3[0].is_number_integer());
|
||||
|
||||
const Json j4 = true;
|
||||
CHECK(j4.is_boolean());
|
||||
}
|
||||
|
||||
SECTION("value_t::number_unsigned creates a number_integer")
|
||||
{
|
||||
const Json j(nlohmann::detail::value_t::number_unsigned);
|
||||
CHECK(j.is_number_integer());
|
||||
CHECK(!j.is_number_unsigned());
|
||||
CHECK(j == 0);
|
||||
CHECK(j.dump() == "0");
|
||||
}
|
||||
|
||||
SECTION("conversions")
|
||||
{
|
||||
Json j = 42;
|
||||
CHECK(j.template get<unsigned>() == 42u);
|
||||
CHECK(j.template get<std::uint64_t>() == 42u);
|
||||
CHECK(j.template get<int>() == 42);
|
||||
CHECK(j.template get<double>() == 42.0);
|
||||
CHECK(j.template get_ptr<integer_t*>() != nullptr);
|
||||
CHECK(*j.template get_ptr<integer_t*>() == 42);
|
||||
CHECK(j.template get_ref<integer_t&>() == 42);
|
||||
j.template get_ref<integer_t&>() = 43;
|
||||
CHECK(j == 43);
|
||||
|
||||
integer_t i = 0;
|
||||
j.get_to(i);
|
||||
CHECK(i == 43);
|
||||
|
||||
const Json object = {{"a", 1}};
|
||||
CHECK(object.value("a", 0u) == 1u);
|
||||
CHECK(object.value("b", 7u) == 7u);
|
||||
}
|
||||
|
||||
SECTION("conversion from a basic_json with distinct types")
|
||||
{
|
||||
const nlohmann::json j = {1, 2u, -3, 4.5};
|
||||
CHECK(j[1].is_number_unsigned());
|
||||
const Json converted = j;
|
||||
CHECK(converted.dump() == "[1,2,-3,4.5]");
|
||||
CHECK(converted[1].is_number_integer());
|
||||
CHECK(!converted[1].is_number_unsigned());
|
||||
|
||||
const nlohmann::json back = converted;
|
||||
CHECK(back == j);
|
||||
}
|
||||
|
||||
SECTION("comparison and hashing")
|
||||
{
|
||||
const Json j1 = 1;
|
||||
const Json j2 = 2u;
|
||||
CHECK(j1 < j2);
|
||||
CHECK(j1 != j2);
|
||||
CHECK(j2 == 2);
|
||||
CHECK(j2 == 2u);
|
||||
CHECK(j1 < 1.5);
|
||||
CHECK(Json(-1) < Json(0u));
|
||||
CHECK(std::hash<Json> {}(Json(5u)) == std::hash<Json> {}(Json(5)));
|
||||
}
|
||||
|
||||
SECTION("JSON Patch, diff, and flatten")
|
||||
{
|
||||
const Json source = Json::parse(R"({"a": [1, 2, 3], "b": 4})");
|
||||
const Json target = Json::parse(R"({"a": [1, 5], "c": 6})");
|
||||
CHECK(source.patch(Json::diff(source, target)) == target);
|
||||
CHECK(source.flatten().unflatten() == source);
|
||||
}
|
||||
|
||||
SECTION("binary formats")
|
||||
{
|
||||
const Json j = {{"min", limits::min()}, {"max", limits::max()}, {"zero", 0}, {"small", 100}, {"negative", -100}, {"float", 1.5}};
|
||||
|
||||
CHECK(Json::from_cbor(Json::to_cbor(j)) == j);
|
||||
CHECK(Json::from_msgpack(Json::to_msgpack(j)) == j);
|
||||
CHECK(Json::from_ubjson(Json::to_ubjson(j)) == j);
|
||||
CHECK(Json::from_bjdata(Json::to_bjdata(j)) == j);
|
||||
CHECK(Json::from_bson(Json::to_bson(j)) == j);
|
||||
CHECK(Json::from_bon8(Json::to_bon8(j)) == j);
|
||||
|
||||
for (const auto& element : Json::from_cbor(Json::to_cbor(j)))
|
||||
{
|
||||
CHECK(!element.is_number_unsigned());
|
||||
}
|
||||
|
||||
// CBOR unsigned integer 2^64-1 does not fit and is read as float
|
||||
const std::vector<std::uint8_t> cbor_max = {0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
const Json from_cbor = Json::from_cbor(cbor_max);
|
||||
CHECK(from_cbor.is_number_float());
|
||||
CHECK(from_cbor.template get<double>() == 18446744073709551615.0);
|
||||
|
||||
// MessagePack uint 8 is read as number_integer
|
||||
const std::vector<std::uint8_t> msgpack_uint8 = {0xCC, 0xC8};
|
||||
const Json from_msgpack = Json::from_msgpack(msgpack_uint8);
|
||||
CHECK(from_msgpack.is_number_integer());
|
||||
CHECK(from_msgpack == 200);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("same type for number_integer_t and number_unsigned_t: type traits")
|
||||
{
|
||||
CHECK(std::is_same<nlohmann::detail::number_unsigned_overload_t<json64>, nlohmann::detail::same_as_number_integer_type>::value);
|
||||
CHECK(std::is_same<nlohmann::detail::number_unsigned_overload_t<nlohmann::json>, std::uint64_t>::value);
|
||||
CHECK(nlohmann::detail::is_compatible_number_integer_type<json64, unsigned>::value);
|
||||
CHECK(!nlohmann::detail::is_compatible_number_integer_type<nlohmann::json, unsigned>::value);
|
||||
CHECK(!nlohmann::detail::is_compatible_number_integer_type<json64, bool>::value);
|
||||
}
|
||||
Reference in new issue
Block a user