mirror of
https://github.com/nlohmann/json.git
synced 2026-10-15 10:47:13 +00:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3aa3d87983 | ||
|
|
5010011656 | ||
|
|
bbb33374d4 | ||
|
|
e26e1ad312 | ||
|
|
7461a199b5 | ||
|
|
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)
|
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_include_directories(single_${RELATIVE_SRC_FILE} PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
||||||
target_compile_features(single_${RELATIVE_SRC_FILE} PRIVATE cxx_std_11)
|
target_compile_features(single_${RELATIVE_SRC_FILE} PRIVATE cxx_std_11)
|
||||||
if(RELATIVE_SRC_FILE STREQUAL "json" OR RELATIVE_SRC_FILE STREQUAL "json_literals")
|
if(RELATIVE_SRC_FILE STREQUAL "json")
|
||||||
# see below: report the diagnostics of json.hpp and json_literals.hpp without --error, so they
|
# see below: report the diagnostics of json.hpp without --error, so they do not fail the build
|
||||||
# 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)
|
set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${IWYU_TOOL} -Xiwyu --max_line_length=300)
|
||||||
else()
|
else()
|
||||||
set_property(TARGET single_${RELATIVE_SRC_FILE} PROPERTY CXX_INCLUDE_WHAT_YOU_USE "${iwyu_path_and_options}")
|
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
|
# 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
|
# 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.
|
# 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
|
if(NOT RELATIVE_SRC_FILE STREQUAL "json")
|
||||||
# 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")
|
|
||||||
list(APPEND single_binaries_tus src_single/${RELATIVE_SRC_FILE}.cpp)
|
list(APPEND single_binaries_tus src_single/${RELATIVE_SRC_FILE}.cpp)
|
||||||
endif()
|
endif()
|
||||||
endforeach()
|
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)
|
if(IWYU_TOOL_PY)
|
||||||
add_custom_target(ci_single_binaries
|
add_custom_target(ci_single_binaries
|
||||||
DEPENDS ${single_binaries}
|
DEPENDS ${single_binaries}
|
||||||
COMMAND ${IWYU_TOOL_PY} -p ${PROJECT_BINARY_DIR} ${single_binaries_tus} -- ${iwyu_options}
|
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"
|
COMMENT "Check if headers are self-contained"
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ Currently, only `remove`, `add`, and `replace` operations are generated.
|
|||||||
- [patch](patch.md) applies a JSON Patch
|
- [patch](patch.md) applies a JSON Patch
|
||||||
- [patch_inplace](patch_inplace.md) applies a JSON Patch in place
|
- [patch_inplace](patch_inplace.md) applies a JSON Patch in place
|
||||||
- [merge_patch](merge_patch.md) applies a JSON Merge Patch
|
- [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
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -285,6 +285,7 @@ Access to the JSON value
|
|||||||
### JSON Merge Patch functions
|
### JSON Merge Patch functions
|
||||||
|
|
||||||
- [**merge_patch**](merge_patch.md) - applies a JSON Merge Patch
|
- [**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
|
## Static functions
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
- [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396)
|
||||||
- [patch](patch.md) apply a JSON patch
|
- [patch](patch.md) apply a JSON patch
|
||||||
|
- [merge_diff](merge_diff.md) creates a diff as a JSON Merge Patch
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -11,12 +11,10 @@ template<typename ScalarType>
|
|||||||
bool operator==(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2)
|
bool operator==(ScalarType lhs, const const_reference rhs) noexcept(/* see below */); // (2)
|
||||||
|
|
||||||
// since C++20
|
// since C++20
|
||||||
class basic_json {
|
bool operator==(const_reference rhs) const noexcept; // (1)
|
||||||
bool operator==(const_reference rhs) const noexcept; // (1)
|
|
||||||
|
|
||||||
template<typename ScalarType>
|
template<typename ScalarType>
|
||||||
bool operator==(ScalarType rhs) const noexcept(/* see below */); // (2)
|
bool operator==(ScalarType rhs) const noexcept(/* see below */); // (2)
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Compares two JSON values for equality according to the following rules:
|
1. Compares two JSON values for equality according to the following rules:
|
||||||
|
|||||||
@@ -2,12 +2,10 @@
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// since C++20
|
// since C++20
|
||||||
class basic_json {
|
std::partial_ordering operator<=>(const_reference rhs) const noexcept; // (1)
|
||||||
std::partial_ordering operator<=>(const_reference rhs) const noexcept; // (1)
|
|
||||||
|
|
||||||
template<typename ScalarType>
|
template<typename ScalarType>
|
||||||
std::partial_ordering operator<=>(const ScalarType rhs) const noexcept(/* see below */); // (2)
|
std::partial_ordering operator<=>(const ScalarType rhs) const noexcept(/* see below */); // (2)
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
1. 3-way compares two JSON values producing a result of type `std::partial_ordering` according to the following rules:
|
1. 3-way compares two JSON values producing a result of type `std::partial_ordering` according to the following rules:
|
||||||
|
|||||||
@@ -18,13 +18,11 @@ bool operator==(
|
|||||||
const json_pointer<RefStringTypeRhs>& rhs); // (2)
|
const json_pointer<RefStringTypeRhs>& rhs); // (2)
|
||||||
|
|
||||||
// since C++20
|
// since C++20
|
||||||
class json_pointer {
|
template<typename RefStringTypeRhs>
|
||||||
template<typename RefStringTypeRhs>
|
bool operator==(
|
||||||
bool operator==(
|
|
||||||
const json_pointer<RefStringTypeRhs>& rhs) const noexcept; // (1)
|
const json_pointer<RefStringTypeRhs>& rhs) const noexcept; // (1)
|
||||||
|
|
||||||
bool operator==(const string_t& rhs) const; // (2)
|
bool operator==(const string_t& rhs) const; // (2)
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Compares two JSON pointers for equality by comparing their reference tokens.
|
1. Compares two JSON pointers for equality by comparing their reference tokens.
|
||||||
|
|||||||
@@ -2,10 +2,8 @@
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// since C++20
|
// since C++20
|
||||||
class json_pointer {
|
template<typename RefStringTypeRhs>
|
||||||
template<typename RefStringTypeRhs>
|
std::strong_ordering operator<=>(const json_pointer<RefStringTypeRhs>& rhs) const noexcept; // *NOPAD*
|
||||||
std::strong_ordering operator<=>(const json_pointer<RefStringTypeRhs>& rhs) const noexcept; // *NOPAD*
|
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
3-way compares two JSON pointers by lexicographically comparing their sequences of reference tokens: corresponding
|
3-way compares two JSON pointers by lexicographically comparing their sequences of reference tokens: corresponding
|
||||||
|
|||||||
@@ -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
|
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.
|
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"
|
!!! 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.
|
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 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
|
- [JSON Pointer](json_pointer.md) - the addressing scheme used by JSON Patch
|
||||||
- Function [`merge_patch`](../api/basic_json/merge_patch.md)
|
- 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
|
- Function [`update`](../api/basic_json/update.md) - merge objects, optionally recursively
|
||||||
@@ -185,9 +185,11 @@ The lexer detects two optional extensions at compile time. Only `iterator_input_
|
|||||||
random-access input of single-byte characters:
|
random-access input of single-byte characters:
|
||||||
|
|
||||||
- `supports_seek`, `get_consumed_count()`, and `copy_consumed_range()` let the lexer reconstruct already consumed input
|
- `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
|
- `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
|
The function `input_adapter` picks the right adapter for the argument passed to `parse`, `accept`, `sax_parse`, or the
|
||||||
`from_*` functions:
|
`from_*` functions:
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ nav:
|
|||||||
- 'json_serializer': api/basic_json/json_serializer.md
|
- 'json_serializer': api/basic_json/json_serializer.md
|
||||||
- 'max_size': api/basic_json/max_size.md
|
- 'max_size': api/basic_json/max_size.md
|
||||||
- 'meta': api/basic_json/meta.md
|
- 'meta': api/basic_json/meta.md
|
||||||
|
- 'merge_diff': api/basic_json/merge_diff.md
|
||||||
- 'merge_patch': api/basic_json/merge_patch.md
|
- 'merge_patch': api/basic_json/merge_patch.md
|
||||||
- 'number_float_t': api/basic_json/number_float_t.md
|
- 'number_float_t': api/basic_json/number_float_t.md
|
||||||
- 'number_integer_t': api/basic_json/number_integer_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
|
#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
|
/// the 128-bit product of two 64-bit numbers
|
||||||
struct uint128_parts
|
struct uint128_parts
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm> // min
|
#include <algorithm> // count, find, min
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint32_t
|
#include <cstdint> // uint32_t
|
||||||
#include <cstring> // strlen
|
#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 <streambuf> // streambuf
|
||||||
#include <string> // string, char_traits
|
#include <string> // string, char_traits
|
||||||
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
||||||
@@ -297,6 +297,7 @@ class iterator_input_adapter
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
++eof_reads;
|
||||||
return char_traits<char_type>::eof();
|
return char_traits<char_type>::eof();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,6 +307,26 @@ class iterator_input_adapter
|
|||||||
return static_cast<std::size_t>(std::distance(begin, current));
|
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
|
// append the already-consumed characters in the half-open range
|
||||||
// [first_index, last_index) to @a out; only valid when supports_seek
|
// [first_index, last_index) to @a out; only valid when supports_seek
|
||||||
template<typename ContainerType>
|
template<typename ContainerType>
|
||||||
@@ -422,6 +443,8 @@ class iterator_input_adapter
|
|||||||
IteratorType begin;
|
IteratorType begin;
|
||||||
IteratorType current;
|
IteratorType current;
|
||||||
SentinelType end;
|
SentinelType end;
|
||||||
|
/// number of get_character() calls that returned EOF
|
||||||
|
std::size_t eof_reads = 0;
|
||||||
|
|
||||||
template<typename BaseInputAdapter, size_t T>
|
template<typename BaseInputAdapter, size_t T>
|
||||||
friend struct wide_string_input_helper;
|
friend struct wide_string_input_helper;
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ struct diagnostic_positions
|
|||||||
{
|
{
|
||||||
// Lexer has read the first character of the container, so
|
// Lexer has read the first character of the container, so
|
||||||
// subtract 1 from the position to get the correct start position.
|
// 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)
|
if (lexer)
|
||||||
{
|
{
|
||||||
// Lexer's position is past the closing brace or bracket, so set that as the end position.
|
// 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.
|
// 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
|
// The start position will be set below based on the length of the string representation
|
||||||
// of the value.
|
// of the value.
|
||||||
v.end_position = lexer->get_position();
|
v.end_position = lexer->get_chars_read_total();
|
||||||
|
|
||||||
switch (v.type())
|
switch (v.type())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include <utility> // move
|
#include <utility> // move
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
|
#include <nlohmann/detail/bit_ops.hpp>
|
||||||
#include <nlohmann/detail/input/input_adapters.hpp>
|
#include <nlohmann/detail/input/input_adapters.hpp>
|
||||||
#include <nlohmann/detail/input/number_parse.hpp>
|
#include <nlohmann/detail/input/number_parse.hpp>
|
||||||
#include <nlohmann/detail/input/position_t.hpp>
|
#include <nlohmann/detail/input/position_t.hpp>
|
||||||
@@ -186,6 +187,15 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
static constexpr bool lazy_token_string =
|
static constexpr bool lazy_token_string =
|
||||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
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
|
/// whether a simulated unget can be passed on to the input adapter, which
|
||||||
/// then leaves the character in the input; see
|
/// then leaves the character in the input; see
|
||||||
/// input_adapter_supports_lookahead
|
/// input_adapter_supports_lookahead
|
||||||
@@ -195,7 +205,9 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
/// whether string scanning may bulk-consume runs of ordinary characters
|
/// whether string scanning may bulk-consume runs of ordinary characters
|
||||||
/// directly from a contiguous input buffer (SWAR fast path). This requires
|
/// directly from a contiguous input buffer (SWAR fast path). This requires
|
||||||
/// the token to be reconstructible lazily (lazy_token_string), so bypassing
|
/// 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 =
|
static constexpr bool bulk_scan =
|
||||||
lazy_token_string
|
lazy_token_string
|
||||||
&& input_adapter_supports_bulk_scan<InputAdapterType>(is_detected<detect_supports_bulk_scan, InputAdapterType> {});
|
&& 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);
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||||
ia.bulk_skip(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
|
/// streaming input: no bulk fast path
|
||||||
@@ -1567,8 +1575,6 @@ scan_number_done:
|
|||||||
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
||||||
{
|
{
|
||||||
ia.bulk_skip(len - 1);
|
ia.bulk_skip(len - 1);
|
||||||
position.chars_read_total += (len - 1);
|
|
||||||
position.chars_read_current_line += (len - 1);
|
|
||||||
return integer_result;
|
return integer_result;
|
||||||
}
|
}
|
||||||
// The value does not fit an integer, so this token converts as a
|
// 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;
|
decimal_point_position = dot_index;
|
||||||
|
|
||||||
ia.bulk_skip(len - 1);
|
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);
|
return convert_number(number_type, mantissa_end);
|
||||||
}
|
}
|
||||||
@@ -1637,7 +1641,7 @@ scan_number_done:
|
|||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
// the first character of the token has already been read, hence the -1
|
// 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
|
#endif
|
||||||
|
|
||||||
note_token_start(std::integral_constant<bool, lazy_token_string> {});
|
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
|
/// per-character position counters (line-count-on-'\n' bookkeeping is
|
||||||
/// handled afterwards, in track_after_read(), once `current` is known)
|
/// handled afterwards, in track_after_read(), once `current` is known)
|
||||||
void advance_position() noexcept
|
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_total;
|
||||||
++position.chars_read_current_line;
|
++position.chars_read_current_line;
|
||||||
@@ -1703,7 +1716,16 @@ scan_number_done:
|
|||||||
// seekable adapters reconstruct the token lazily on error (see
|
// seekable adapters reconstruct the token lazily on error (see
|
||||||
// get_token_string), so the eager per-character copy is skipped
|
// get_token_string), so the eager per-character copy is skipped
|
||||||
capture_char(std::integral_constant<bool, lazy_token_string> {});
|
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')
|
if (current == '\n')
|
||||||
{
|
{
|
||||||
++position.lines_read;
|
++position.lines_read;
|
||||||
@@ -1712,8 +1734,6 @@ scan_number_done:
|
|||||||
chars_read_before_newline = position.chars_read_current_line;
|
chars_read_before_newline = position.chars_read_current_line;
|
||||||
position.chars_read_current_line = 0;
|
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
|
We implement unget by setting variable next_unget to true. The input is not
|
||||||
changed - we just simulate ungetting by modifying chars_read_total,
|
changed - we just simulate ungetting by modifying chars_read_total,
|
||||||
chars_read_current_line, and token_string. The next call to get() will
|
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()
|
void unget()
|
||||||
{
|
{
|
||||||
next_unget = true;
|
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;
|
--position.chars_read_total;
|
||||||
|
|
||||||
// in case we "unget" a newline, we have to also decrement the lines_read
|
// 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;
|
--position.chars_read_current_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// adapter without lookahead: nothing to do (see release_lookahead)
|
/// adapter without lookahead: nothing to do (see release_lookahead)
|
||||||
@@ -1857,10 +1885,21 @@ scan_number_done:
|
|||||||
// diagnostics
|
// 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
|
#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
|
/// 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
|
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
|
// into one that reloads the pointer from memory every character
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
if (current == '\n')
|
||||||
|
{
|
||||||
|
skip_indentation(std::integral_constant<bool, bulk_scan> {});
|
||||||
|
}
|
||||||
get_ignoring_pending_unget();
|
get_ignoring_pending_unget();
|
||||||
}
|
}
|
||||||
while (current_is_whitespace());
|
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()
|
token_type scan()
|
||||||
{
|
{
|
||||||
// initially, skip the BOM
|
// 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";
|
error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given";
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
@@ -2039,7 +2157,7 @@ scan_number_done:
|
|||||||
token_type scan_expecting(token_type expected_type)
|
token_type scan_expecting(token_type expected_type)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(expected_type == token_type::name_separator || expected_type == token_type::value_separator);
|
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) ? ':' : ',');
|
const char_int_type expected_char = static_cast<unsigned char>((expected_type == token_type::name_separator) ? ':' : ',');
|
||||||
skip_whitespace();
|
skip_whitespace();
|
||||||
if (JSON_HEDLEY_LIKELY(current == expected_char))
|
if (JSON_HEDLEY_LIKELY(current == expected_char))
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ class parser
|
|||||||
}
|
}
|
||||||
case token_type::end_of_input:
|
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");
|
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
|
// MSVC 2015 reports C4100 (unreferenced parameter) when SAX::parse_error is static
|
||||||
static_cast<void>(sax);
|
static_cast<void>(sax);
|
||||||
return sax.parse_error(m_lexer.get_position(), m_lexer.get_token_string(),
|
// computing the position is O(input read so far) for seekable input
|
||||||
parse_error::create(exception_id::syntax_error, m_lexer.get_position(), message, nullptr));
|
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)
|
std::string exception_message(const token_type expected, const std::string& context)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
|
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <nlohmann/detail/bit_ops.hpp>
|
||||||
#include <nlohmann/detail/input/binary_reader.hpp>
|
#include <nlohmann/detail/input/binary_reader.hpp>
|
||||||
#include <nlohmann/detail/input/string_scan.hpp>
|
#include <nlohmann/detail/input/string_scan.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
@@ -204,24 +205,12 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
case value_t::number_integer:
|
case value_t::number_integer:
|
||||||
{
|
|
||||||
if (j.m_data.m_value.number_integer >= 0)
|
|
||||||
{
|
|
||||||
// CBOR does not differentiate between positive signed
|
|
||||||
// integers and unsigned integers
|
|
||||||
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// a negative integer n is encoded as -1 - n
|
|
||||||
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::number_unsigned:
|
case value_t::number_unsigned:
|
||||||
{
|
{
|
||||||
write_cbor_head(0x00, j.m_data.m_value.number_unsigned);
|
// one case for both, so that an array mixing negative
|
||||||
|
// (number_integer) and non-negative (number_unsigned) values
|
||||||
|
// does not mispredict the switch
|
||||||
|
write_cbor_integer(j);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,13 +245,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
{
|
{
|
||||||
// step 1: write control byte and the array size
|
write_cbor_array(j, depth);
|
||||||
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
|
||||||
|
|
||||||
for (const auto& el : *j.m_data.m_value.array)
|
|
||||||
{
|
|
||||||
write_cbor(el, depth + 1);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,54 +403,12 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
case value_t::number_integer:
|
case value_t::number_integer:
|
||||||
{
|
|
||||||
if (j.m_data.m_value.number_integer >= 0)
|
|
||||||
{
|
|
||||||
// MessagePack does not differentiate between positive
|
|
||||||
// signed integers and unsigned integers.
|
|
||||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (j.m_data.m_value.number_integer >= -32)
|
|
||||||
{
|
|
||||||
// negative fixnum
|
|
||||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
|
||||||
}
|
|
||||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
|
|
||||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
|
||||||
{
|
|
||||||
// int 8
|
|
||||||
oa.write_character(to_char_type(0xD0));
|
|
||||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
|
||||||
}
|
|
||||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
|
|
||||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
|
||||||
{
|
|
||||||
// int 16
|
|
||||||
oa.write_character(to_char_type(0xD1));
|
|
||||||
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
|
||||||
}
|
|
||||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
|
|
||||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
|
||||||
{
|
|
||||||
// int 32
|
|
||||||
oa.write_character(to_char_type(0xD2));
|
|
||||||
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// int 64
|
|
||||||
oa.write_character(to_char_type(0xD3));
|
|
||||||
write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::number_unsigned:
|
case value_t::number_unsigned:
|
||||||
{
|
{
|
||||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
// one case for both, so that an array mixing negative
|
||||||
|
// (number_integer) and non-negative (number_unsigned) values
|
||||||
|
// does not mispredict the switch
|
||||||
|
write_msgpack_integer(j, std::integral_constant<bool, msgpack_integers_fit_64_bits()>());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,13 +426,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
{
|
{
|
||||||
// step 1: write control byte and the array size
|
write_msgpack_array(j, depth);
|
||||||
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
|
||||||
|
|
||||||
for (const auto& el : *j.m_data.m_value.array)
|
|
||||||
{
|
|
||||||
write_msgpack(el, depth + 1);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1951,6 +1886,316 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief size class of an unsigned integer from its bit length
|
||||||
|
|
||||||
|
1, 2, 3, or 4 if @a x fits 1, 2, 4, or 8 bytes. Looked up from the number
|
||||||
|
of leading zero bits instead of found by branches: the sizes of a stream
|
||||||
|
of numbers are hard to predict, and a mispredicted branch costs more than
|
||||||
|
writing the number. (A table, as comparisons are vectorized by clang into
|
||||||
|
slower code.)
|
||||||
|
*/
|
||||||
|
static unsigned integer_size_class(const std::uint64_t x) noexcept
|
||||||
|
{
|
||||||
|
static constexpr std::array<std::uint8_t, 64> size_class_by_leading_zeros =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||||
|
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||||
|
2, 2, 2, 2, 2, 2, 2, 2,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// x | 1 keeps count_leading_zeros() away from 0
|
||||||
|
return size_class_by_leading_zeros[static_cast<std::size_t>(count_leading_zeros(x | 1u))];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief encode @a first_byte and the low-order bytes of @a value that
|
||||||
|
@a size_class calls for (none, or 1, 2, 4, or 8) in network order
|
||||||
|
to @a out
|
||||||
|
@return the number of characters of the encoding; up to
|
||||||
|
@ref integer_encoding_space characters at @a out are overwritten
|
||||||
|
|
||||||
|
The bytes are written as one byte and one 8-byte word whose bytes after
|
||||||
|
the value are scratch that the caller drops, so that writing them does not
|
||||||
|
depend on the size class. For class 0 the word is all scratch; it is
|
||||||
|
shifted by 60 bits rather than 64, which would be undefined.
|
||||||
|
*/
|
||||||
|
std::size_t encode_first_byte_and_tail(CharType* out, const std::uint8_t first_byte, const unsigned size_class, const std::uint64_t value) const noexcept
|
||||||
|
{
|
||||||
|
out[0] = to_char_type(first_byte);
|
||||||
|
const unsigned unused_bits = 64u - ((8u << size_class) >> 1u);
|
||||||
|
const std::uint64_t tail = is_little_endian ? byte_swap(value) >> unused_bits : value << unused_bits;
|
||||||
|
std::memcpy(out + 1, &tail, sizeof(tail));
|
||||||
|
return ((std::size_t{1} << size_class) >> 1u) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// characters encode_first_byte_and_tail() may overwrite: the first byte
|
||||||
|
/// and one 8-byte word
|
||||||
|
static constexpr std::size_t integer_encoding_space = 9;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief the 64 bits of a number_integer or number_unsigned value, and a mask
|
||||||
|
that is all ones if it is negative, else zero
|
||||||
|
|
||||||
|
Selected through masks rather than branches, as the type and the sign of
|
||||||
|
the values in an array of mixed signs alternate unpredictably (non-negative
|
||||||
|
integers are number_unsigned after parsing). Up to 64 bits, the sign is bit
|
||||||
|
63 of the result (a narrower integer is sign-extended); computing it with
|
||||||
|
masks keeps GCC from branching on the type. A wider integer has to be
|
||||||
|
compared.
|
||||||
|
*/
|
||||||
|
static std::uint64_t integer_bits(const BasicJsonType& j, std::uint64_t& sign_mask) noexcept
|
||||||
|
{
|
||||||
|
const bool is_signed = j.m_data.m_type == value_t::number_integer;
|
||||||
|
const std::uint64_t raw = is_signed
|
||||||
|
? static_cast<std::uint64_t>(j.m_data.m_value.number_integer)
|
||||||
|
: static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned);
|
||||||
|
sign_mask = sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
||||||
|
? (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u))
|
||||||
|
: std::uint64_t{0} - static_cast<std::uint64_t>(is_signed && j.m_data.m_value.number_integer < 0);
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief encode the head of a CBOR integer to @a out without branching on its
|
||||||
|
size
|
||||||
|
@return the number of characters of the head
|
||||||
|
|
||||||
|
Same output as @ref write_cbor_head. The lengths of strings and containers
|
||||||
|
are mostly predictable, so their heads keep the branches; the sizes of a
|
||||||
|
run of integers are not, so an integer's head is assembled without them.
|
||||||
|
*/
|
||||||
|
std::size_t encode_cbor_integer_head(CharType* out, const std::uint8_t major_type, const std::uint64_t argument) const noexcept
|
||||||
|
{
|
||||||
|
// an argument of at most 23 is in size class 1 (one byte) and is moved
|
||||||
|
// to class 0, the first byte, by subtracting 1
|
||||||
|
const auto is_immediate = static_cast<unsigned>(argument <= 0x17u);
|
||||||
|
const unsigned size_class = integer_size_class(argument) - is_immediate;
|
||||||
|
|
||||||
|
// the first byte: the argument itself if it is immediate, else
|
||||||
|
// 0x17 + size_class, selected through a mask
|
||||||
|
const std::uint64_t immediate_mask = std::uint64_t{0} - is_immediate;
|
||||||
|
const auto first_byte = static_cast<std::uint8_t>(major_type + ((argument & immediate_mask) | ((0x17u + size_class) & ~immediate_mask)));
|
||||||
|
return encode_first_byte_and_tail(out, first_byte, size_class, argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief write an array value (head and elements) as CBOR
|
||||||
|
|
||||||
|
Integer elements, the most common scalar, are written without a call to
|
||||||
|
@ref write_cbor. The loop is kept out of line: inlined into write_cbor, its
|
||||||
|
registers would be saved and restored on every call of write_cbor, which
|
||||||
|
is one call per value nested in an object.
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
void write_cbor_array(const BasicJsonType& j, const std::size_t depth)
|
||||||
|
{
|
||||||
|
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
||||||
|
write_array_elements(j, [this](CharType * out, const BasicJsonType & el)
|
||||||
|
{
|
||||||
|
return encode_cbor_integer(out, el);
|
||||||
|
}, [this, depth](const BasicJsonType & el)
|
||||||
|
{
|
||||||
|
write_cbor(el, depth + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief write the elements of the array @a j, runs of integers with
|
||||||
|
@a encode_integer and all other values with @a write_value
|
||||||
|
|
||||||
|
Runs of integers are encoded straight into space reserved from the sink
|
||||||
|
for a whole run: the write pointer stays a local, where it would otherwise
|
||||||
|
be reloaded from the sink for every value, as a store of a character may
|
||||||
|
alias it.
|
||||||
|
*/
|
||||||
|
template<typename EncodeInteger, typename WriteValue>
|
||||||
|
void write_array_elements(const BasicJsonType& j, EncodeInteger encode_integer, WriteValue write_value)
|
||||||
|
{
|
||||||
|
constexpr std::size_t max_run = max_reserve_size / integer_encoding_space;
|
||||||
|
auto it = j.m_data.m_value.array->begin();
|
||||||
|
const auto end = j.m_data.m_value.array->end();
|
||||||
|
while (it != end)
|
||||||
|
{
|
||||||
|
if (it->is_number_integer())
|
||||||
|
{
|
||||||
|
CharType* const start = oa.reserve(max_run * integer_encoding_space);
|
||||||
|
CharType* out = start;
|
||||||
|
for (std::size_t n = 0; n < max_run && it != end && it->is_number_integer(); ++n, ++it)
|
||||||
|
{
|
||||||
|
out += encode_integer(out, *it);
|
||||||
|
}
|
||||||
|
oa.commit(static_cast<std::size_t>(out - start));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write_value(*it);
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief write a number_integer or number_unsigned value as a CBOR integer
|
||||||
|
|
||||||
|
A negative integer n has major type 1 and the argument -1 - n, which is ~n.
|
||||||
|
Kept out of line, like the other per-value paths, so that write_cbor stays
|
||||||
|
small (runs of integers in an array are encoded inline instead).
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
void write_cbor_integer(const BasicJsonType& j)
|
||||||
|
{
|
||||||
|
oa.commit(encode_cbor_integer(oa.reserve(integer_encoding_space), j));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// encode a number_integer or number_unsigned value to @a out (see
|
||||||
|
/// @ref write_cbor_integer) and return the number of characters
|
||||||
|
std::size_t encode_cbor_integer(CharType* out, const BasicJsonType& j) const noexcept
|
||||||
|
{
|
||||||
|
std::uint64_t sign_mask = 0;
|
||||||
|
const std::uint64_t raw = integer_bits(j, sign_mask);
|
||||||
|
// ~raw is -1 - n in the 64 bits that are written
|
||||||
|
return encode_cbor_integer_head(out, static_cast<std::uint8_t>(sign_mask & 0x20u), raw ^ sign_mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief whether the MessagePack integers of BasicJsonType are written by
|
||||||
|
@ref encode_msgpack_integer
|
||||||
|
|
||||||
|
It picks the format from the low 64 bits; a wider number type keeps the
|
||||||
|
range checks of @ref write_msgpack on the whole value.
|
||||||
|
*/
|
||||||
|
static constexpr bool msgpack_integers_fit_64_bits() noexcept
|
||||||
|
{
|
||||||
|
return sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
||||||
|
&& sizeof(typename BasicJsonType::number_unsigned_t) <= sizeof(std::uint64_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief write a number_integer or number_unsigned value as a MessagePack
|
||||||
|
integer without branching on its sign or size
|
||||||
|
|
||||||
|
Same output as @ref write_msgpack: a non-negative integer as positive fixint
|
||||||
|
(< 128) or uint 8/16/32/64, a negative one as negative fixint (>= -32) or
|
||||||
|
int 8/16/32/64. Kept out of line (see @ref write_cbor_integer).
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
void write_msgpack_integer(const BasicJsonType& j, std::true_type /*fits_64_bits*/)
|
||||||
|
{
|
||||||
|
oa.commit(encode_msgpack_integer(oa.reserve(integer_encoding_space), j));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// encode a number_integer or number_unsigned value to @a out (see
|
||||||
|
/// @ref write_msgpack_integer) and return the number of characters
|
||||||
|
std::size_t encode_msgpack_integer(CharType* out, const BasicJsonType& j) const noexcept
|
||||||
|
{
|
||||||
|
std::uint64_t sign_mask = 0;
|
||||||
|
const std::uint64_t raw = integer_bits(j, sign_mask);
|
||||||
|
|
||||||
|
// A negative n fits int 8/16/32 exactly if 2 * (-1 - n) + 1 fits
|
||||||
|
// uint 8/16/32, so x gives the size class of either sign.
|
||||||
|
const std::uint64_t is_negative = sign_mask & 1u;
|
||||||
|
const std::uint64_t x = ((raw ^ sign_mask) << is_negative) | is_negative;
|
||||||
|
|
||||||
|
// A fixint is at most 127, or at least -32 (x at most 63). It is in
|
||||||
|
// size class 1 and is moved to class 0, the first byte, by subtracting 1.
|
||||||
|
const auto is_fixint = static_cast<unsigned>(x <= (127u ^ (sign_mask & 64u)));
|
||||||
|
const unsigned size_class = integer_size_class(x) - is_fixint;
|
||||||
|
|
||||||
|
// the first byte: the low byte of the integer for a fixint, else the
|
||||||
|
// marker 0xCC..0xCF (uint) or 0xD0..0xD3 (int), selected through a mask
|
||||||
|
const std::uint64_t fixint_mask = std::uint64_t{0} - is_fixint;
|
||||||
|
const std::uint64_t marker = 0xCBu + size_class + (sign_mask & 4u);
|
||||||
|
const auto first_byte = static_cast<std::uint8_t>((raw & fixint_mask) | (marker & ~fixint_mask));
|
||||||
|
return encode_first_byte_and_tail(out, first_byte, size_class, raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// write a number_integer or number_unsigned value of a number type wider
|
||||||
|
/// than 64 bits as a MessagePack integer, with range checks on the whole value
|
||||||
|
void write_msgpack_integer(const BasicJsonType& j, std::false_type /*fits_64_bits*/)
|
||||||
|
{
|
||||||
|
if (j.m_data.m_type == value_t::number_unsigned)
|
||||||
|
{
|
||||||
|
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j.m_data.m_value.number_integer >= 0)
|
||||||
|
{
|
||||||
|
// MessagePack does not differentiate between positive
|
||||||
|
// signed integers and unsigned integers.
|
||||||
|
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (j.m_data.m_value.number_integer >= -32)
|
||||||
|
{
|
||||||
|
// negative fixnum
|
||||||
|
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||||
|
}
|
||||||
|
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
|
||||||
|
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
||||||
|
{
|
||||||
|
// int 8
|
||||||
|
oa.write_character(to_char_type(0xD0));
|
||||||
|
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||||
|
}
|
||||||
|
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
|
||||||
|
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
||||||
|
{
|
||||||
|
// int 16
|
||||||
|
oa.write_character(to_char_type(0xD1));
|
||||||
|
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
||||||
|
}
|
||||||
|
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
|
||||||
|
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
||||||
|
{
|
||||||
|
// int 32
|
||||||
|
oa.write_character(to_char_type(0xD2));
|
||||||
|
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// int 64
|
||||||
|
oa.write_character(to_char_type(0xD3));
|
||||||
|
write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief write an array value (head and elements) as MessagePack
|
||||||
|
|
||||||
|
See @ref write_cbor_array.
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
void write_msgpack_array(const BasicJsonType& j, const std::size_t depth)
|
||||||
|
{
|
||||||
|
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
||||||
|
write_msgpack_array_elements(j, depth, std::integral_constant<bool, msgpack_integers_fit_64_bits()>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_msgpack_array_elements(const BasicJsonType& j, const std::size_t depth, std::true_type /*fits_64_bits*/)
|
||||||
|
{
|
||||||
|
write_array_elements(j, [this](CharType * out, const BasicJsonType & el)
|
||||||
|
{
|
||||||
|
return encode_msgpack_integer(out, el);
|
||||||
|
}, [this, depth](const BasicJsonType & el)
|
||||||
|
{
|
||||||
|
write_msgpack(el, depth + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_msgpack_array_elements(const BasicJsonType& j, const std::size_t depth, std::false_type /*fits_64_bits*/)
|
||||||
|
{
|
||||||
|
for (const auto& el : *j.m_data.m_value.array)
|
||||||
|
{
|
||||||
|
write_msgpack(el, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief write an object key as a CBOR text string
|
@brief write an object key as a CBOR text string
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm> // max
|
||||||
|
#include <array> // array
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
|
#include <cstring> // memcpy
|
||||||
#include <memory> // shared_ptr, make_shared
|
#include <memory> // shared_ptr, make_shared
|
||||||
#include <string> // basic_string
|
#include <string> // basic_string
|
||||||
#include <type_traits> // conditional, integral_constant, is_same
|
#include <type_traits> // conditional, integral_constant, is_same
|
||||||
@@ -48,32 +51,170 @@ template<typename CharType> struct output_adapter_protocol
|
|||||||
template<typename CharType>
|
template<typename CharType>
|
||||||
using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
|
using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
|
||||||
|
|
||||||
|
/// the largest number of characters a writer may reserve() from a sink at once;
|
||||||
|
/// output_vector_sink also appends writes of at least this size with insert()
|
||||||
|
constexpr std::size_t max_reserve_size = 256;
|
||||||
|
|
||||||
/// @brief non-virtual output sink writing into a std::vector
|
/// @brief non-virtual output sink writing into a std::vector
|
||||||
///
|
///
|
||||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||||
/// passed to binary_writer by value as a template parameter, so
|
/// passed to binary_writer by value as a template parameter, so
|
||||||
/// write_character()/write_characters() are ordinary (inlinable) calls with no
|
/// write_character()/write_characters() are ordinary (inlinable) calls with no
|
||||||
/// vtable lookup and no shared_ptr. It is used for the common
|
/// vtable lookup and no shared_ptr. It is used for the common
|
||||||
/// `to_cbor`/`to_msgpack`/... into a std::vector. output_vector_adapter below
|
/// `to_cbor`/`to_msgpack`/... into a std::vector.
|
||||||
/// wraps this same sink to provide the virtual interface.
|
///
|
||||||
|
/// Most writes are a few bytes - a type marker, or a number of 1, 2, 4, or 8
|
||||||
|
/// bytes - and appending those with push_back() or insert() cost more than
|
||||||
|
/// encoding them: insert() is an out-of-line call into a routine that can insert
|
||||||
|
/// anywhere, and resize() is one as well with libstdc++. The sink therefore keeps
|
||||||
|
/// its own write position: it grows the vector geometrically ahead of the
|
||||||
|
/// position and copies each write to it, so a write is a capacity check and a
|
||||||
|
/// short copy. The destructor shrinks the vector to the bytes actually written,
|
||||||
|
/// also when a write throws; shrinking a vector never allocates. The sink owns
|
||||||
|
/// that trim, so it can be moved but not copied, and the vector must not be used
|
||||||
|
/// while the sink is alive.
|
||||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||||
class output_vector_sink
|
class output_vector_sink
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||||
|
: v(&vec), position(vec.size())
|
||||||
|
{}
|
||||||
|
|
||||||
|
output_vector_sink(const output_vector_sink&) = delete;
|
||||||
|
output_vector_sink& operator=(const output_vector_sink&) = delete;
|
||||||
|
output_vector_sink& operator=(output_vector_sink&&) = delete;
|
||||||
|
|
||||||
|
output_vector_sink(output_vector_sink&& other) noexcept
|
||||||
|
: v(other.v), position(other.position)
|
||||||
|
{
|
||||||
|
other.v = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
~output_vector_sink()
|
||||||
|
{
|
||||||
|
if (v != nullptr)
|
||||||
|
{
|
||||||
|
v->resize(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// always inlined: GCC otherwise keeps these out of line in a large
|
||||||
|
// translation unit, and a call per byte or number costs more than the write
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
void write_character(CharType c)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(position == v->size()))
|
||||||
|
{
|
||||||
|
grow(1);
|
||||||
|
}
|
||||||
|
(*v)[position++] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||||
|
// pointer with length 0 for empty strings/binary values; memcpy must not
|
||||||
|
// see it.
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
void write_characters(const CharType* s, std::size_t length)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
||||||
|
{
|
||||||
|
if (length >= max_reserve_size)
|
||||||
|
{
|
||||||
|
append(s, length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
grow(length);
|
||||||
|
}
|
||||||
|
if (length != 0)
|
||||||
|
{
|
||||||
|
std::memcpy(v->data() + position, s, length * sizeof(CharType));
|
||||||
|
position += length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief space for writing up to @a length characters directly
|
||||||
|
///
|
||||||
|
/// Returns a pointer to at least @a length (at most @ref max_reserve_size)
|
||||||
|
/// writable characters at the write position; @ref commit then keeps the
|
||||||
|
/// first ones of them. A writer can store whole words there and keep only
|
||||||
|
/// the bytes it needs, and keep its write pointer in a register across many
|
||||||
|
/// values instead of updating the sink after each.
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
CharType* reserve(const std::size_t length)
|
||||||
|
{
|
||||||
|
JSON_ASSERT(length <= max_reserve_size);
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
||||||
|
{
|
||||||
|
grow(length);
|
||||||
|
}
|
||||||
|
return v->data() + position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// keep the first @a length characters written to the space from reserve()
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
void commit(const std::size_t length) noexcept
|
||||||
|
{
|
||||||
|
position += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// make room for at least @a length more characters after the position;
|
||||||
|
/// kept out of line so that the write functions stay small enough for
|
||||||
|
/// GCC to inline them into the writer
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
void grow(const std::size_t length)
|
||||||
|
{
|
||||||
|
// use the capacity the vector already has, else at least double it;
|
||||||
|
// never less than 64 characters, so that a small value does not
|
||||||
|
// reallocate for every few bytes
|
||||||
|
const std::size_t needed = position + length;
|
||||||
|
v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
|
||||||
|
? v->capacity()
|
||||||
|
: (std::max)(needed, v->capacity() * 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// append @a length (at least max_reserve_size) characters that do not fit
|
||||||
|
/// the space ahead of the position: insert() copies them once into the
|
||||||
|
/// grown vector, where grow() would first zero the new space and then copy
|
||||||
|
/// over it - noticeable for a large string or binary value
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
void append(const CharType* s, const std::size_t length)
|
||||||
|
{
|
||||||
|
v->resize(position);
|
||||||
|
if (v->capacity() - position < length)
|
||||||
|
{
|
||||||
|
// grow geometrically, with room for the writes that follow, so
|
||||||
|
// that the next grow() need not reallocate right away
|
||||||
|
v->reserve((std::max)(v->capacity() * 2, position + length + max_reserve_size));
|
||||||
|
}
|
||||||
|
v->insert(v->end(), s, s + length);
|
||||||
|
position = v->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<CharType, AllocatorType>* v;
|
||||||
|
/// number of characters of *v written so far
|
||||||
|
std::size_t position;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// output adapter for byte vectors
|
||||||
|
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||||
|
class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||||
: v(vec)
|
: v(vec)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void write_character(CharType c)
|
void write_character(CharType c) override
|
||||||
{
|
{
|
||||||
v.push_back(c);
|
v.push_back(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||||
// pointer with length 0 for empty strings/binary values. Appending an empty
|
// pointer with length 0 for empty strings/binary values; appending an empty
|
||||||
// range is a no-op; the type-erased path tolerates this via the (unattributed)
|
// range is a no-op
|
||||||
// virtual base, and the concrete sink must do the same.
|
void write_characters(const CharType* s, std::size_t length) override
|
||||||
void write_characters(const CharType* s, std::size_t length)
|
|
||||||
{
|
{
|
||||||
v.insert(v.end(), s, s + length);
|
v.insert(v.end(), s, s + length);
|
||||||
}
|
}
|
||||||
@@ -82,33 +223,6 @@ class output_vector_sink
|
|||||||
std::vector<CharType, AllocatorType>& v;
|
std::vector<CharType, AllocatorType>& v;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// output adapter for byte vectors
|
|
||||||
///
|
|
||||||
/// The appending itself lives in output_vector_sink; this class only adds the
|
|
||||||
/// virtual output_adapter_protocol interface on top of it, so both the
|
|
||||||
/// type-erased and the templated path share one implementation.
|
|
||||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
|
||||||
class output_vector_adapter : public output_adapter_protocol<CharType>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
|
|
||||||
: sink(vec)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void write_character(CharType c) override
|
|
||||||
{
|
|
||||||
sink.write_character(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_characters(const CharType* s, std::size_t length) override
|
|
||||||
{
|
|
||||||
sink.write_characters(s, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
output_vector_sink<CharType, AllocatorType> sink;
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifndef JSON_NO_IO
|
#ifndef JSON_NO_IO
|
||||||
/// output adapter for output streams
|
/// output adapter for output streams
|
||||||
template<typename CharType>
|
template<typename CharType>
|
||||||
@@ -188,8 +302,24 @@ class output_adapter_sink
|
|||||||
oa->write_characters(s, length);
|
oa->write_characters(s, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// space for writing up to @a length characters (see
|
||||||
|
/// output_vector_sink::reserve)
|
||||||
|
CharType* reserve(const std::size_t length) noexcept
|
||||||
|
{
|
||||||
|
JSON_ASSERT(length <= max_reserve_size);
|
||||||
|
static_cast<void>(length);
|
||||||
|
return reserved.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// write the first @a length characters written to the space from reserve()
|
||||||
|
void commit(const std::size_t length)
|
||||||
|
{
|
||||||
|
oa->write_characters(reserved.data(), length);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
output_adapter_t<CharType> oa;
|
output_adapter_t<CharType> oa;
|
||||||
|
std::array<CharType, max_reserve_size> reserved{};
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
||||||
|
|||||||
+181
-6
@@ -42,9 +42,14 @@
|
|||||||
// each of these, not only the forward declarations from json_fwd.hpp, so IWYU's suggestion to
|
// 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
|
// 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.
|
// 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/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/from_json.hpp> // IWYU pragma: keep
|
||||||
#include <nlohmann/detail/conversions/to_json.hpp> // IWYU pragma: keep
|
#include <nlohmann/detail/conversions/to_json.hpp> // IWYU pragma: keep
|
||||||
#include <nlohmann/detail/exceptions.hpp>
|
#include <nlohmann/detail/exceptions.hpp>
|
||||||
@@ -61,8 +66,8 @@
|
|||||||
#include <nlohmann/detail/iterators/primitive_iterator.hpp>
|
#include <nlohmann/detail/iterators/primitive_iterator.hpp>
|
||||||
#include <nlohmann/detail/json_custom_base_class.hpp>
|
#include <nlohmann/detail/json_custom_base_class.hpp>
|
||||||
#include <nlohmann/detail/json_pointer.hpp>
|
#include <nlohmann/detail/json_pointer.hpp>
|
||||||
#include <nlohmann/detail/json_ref.hpp>
|
#include <nlohmann/detail/json_ref.hpp> // IWYU pragma: export
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp> // IWYU pragma: export
|
||||||
#include <nlohmann/detail/meta/detected.hpp>
|
#include <nlohmann/detail/meta/detected.hpp>
|
||||||
#include <nlohmann/detail/string_concat.hpp>
|
#include <nlohmann/detail/string_concat.hpp>
|
||||||
#include <nlohmann/detail/string_escape.hpp>
|
#include <nlohmann/detail/string_escape.hpp>
|
||||||
@@ -74,7 +79,7 @@
|
|||||||
#include <nlohmann/detail/output/serializer.hpp>
|
#include <nlohmann/detail/output/serializer.hpp>
|
||||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
||||||
#include <nlohmann/detail/value_t.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/ordered_map.hpp> // IWYU pragma: keep
|
||||||
#include <nlohmann/thirdparty/hedley/hedley.hpp>
|
#include <nlohmann/thirdparty/hedley/hedley.hpp>
|
||||||
|
|
||||||
@@ -7449,6 +7454,176 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 +7800,7 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
|
|||||||
// where needed.
|
// where needed.
|
||||||
#ifndef JSON_NO_AUTOMATIC_UDLS
|
#ifndef JSON_NO_AUTOMATIC_UDLS
|
||||||
// NOLINTNEXTLINE(misc-header-include-cycle): json_literals.hpp includes this header
|
// 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
|
||||||
|
|
||||||
#endif // INCLUDE_NLOHMANN_JSON_HPP_
|
#endif // INCLUDE_NLOHMANN_JSON_HPP_
|
||||||
Generated
+1081
-198
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;
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ using nlohmann::json;
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -35,6 +36,17 @@ std::vector<json> test_values()
|
|||||||
big_object[std::to_string(i)] = i;
|
big_object[std::to_string(i)] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// large strings and binary values between small ones: a write too large
|
||||||
|
// for the space the vector sink has ahead takes a different path than a
|
||||||
|
// small one, and the small writes after it must still land behind it
|
||||||
|
json large_payloads = json::array();
|
||||||
|
for (int i = 0; i < 50; ++i)
|
||||||
|
{
|
||||||
|
large_payloads.push_back(std::string(300, static_cast<char>('a' + (i % 26))));
|
||||||
|
large_payloads.push_back(i);
|
||||||
|
large_payloads.push_back(json::binary(std::vector<std::uint8_t>(static_cast<std::size_t>(100 * i), static_cast<std::uint8_t>(i))));
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
{
|
{
|
||||||
json(nullptr), json(true), json(false),
|
json(nullptr), json(true), json(false),
|
||||||
@@ -46,7 +58,8 @@ std::vector<json> test_values()
|
|||||||
json::array(), json::object(),
|
json::array(), json::object(),
|
||||||
json::array({1, 2, 3}), json({{"a", 1}, {"b", nullptr}}),
|
json::array({1, 2, 3}), json({{"a", 1}, {"b", nullptr}}),
|
||||||
json({{"nested", {{"deep", json::array({1, "two", 3.0, nullptr})}}}}),
|
json({{"nested", {{"deep", json::array({1, "two", 3.0, nullptr})}}}}),
|
||||||
big_array, big_object
|
big_array, big_object, large_payloads,
|
||||||
|
json::binary(std::vector<std::uint8_t>(5000, 0x42))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +183,256 @@ TEST_CASE("binary writer output sinks")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
// the CBOR head of an integer, encoded independently of binary_writer
|
||||||
|
void reference_cbor_head(std::vector<std::uint8_t>& out, const std::uint8_t major_type, const std::uint64_t argument)
|
||||||
|
{
|
||||||
|
if (argument <= 23)
|
||||||
|
{
|
||||||
|
out.push_back(static_cast<std::uint8_t>(major_type + argument));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int length = 8;
|
||||||
|
std::uint8_t additional = 27;
|
||||||
|
if (argument <= 0xFF)
|
||||||
|
{
|
||||||
|
length = 1;
|
||||||
|
additional = 24;
|
||||||
|
}
|
||||||
|
else if (argument <= 0xFFFF)
|
||||||
|
{
|
||||||
|
length = 2;
|
||||||
|
additional = 25;
|
||||||
|
}
|
||||||
|
else if (argument <= 0xFFFFFFFF)
|
||||||
|
{
|
||||||
|
length = 4;
|
||||||
|
additional = 26;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push_back(static_cast<std::uint8_t>(major_type + additional));
|
||||||
|
for (int i = length - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
out.push_back(static_cast<std::uint8_t>(argument >> (8 * i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the CBOR encoding of an integer value
|
||||||
|
void reference_cbor_integer(std::vector<std::uint8_t>& out, const json& j)
|
||||||
|
{
|
||||||
|
if (j.is_number_unsigned())
|
||||||
|
{
|
||||||
|
reference_cbor_head(out, 0x00, j.get<std::uint64_t>());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto n = j.get<std::int64_t>();
|
||||||
|
if (n >= 0)
|
||||||
|
{
|
||||||
|
reference_cbor_head(out, 0x00, static_cast<std::uint64_t>(n));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reference_cbor_head(out, 0x20, static_cast<std::uint64_t>(-1 - n));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the MessagePack encoding of an integer value, encoded independently of
|
||||||
|
// binary_writer
|
||||||
|
void reference_msgpack_integer(std::vector<std::uint8_t>& out, const json& j)
|
||||||
|
{
|
||||||
|
const auto put = [&out](const std::uint8_t marker, const std::uint64_t bits, const int length)
|
||||||
|
{
|
||||||
|
out.push_back(marker);
|
||||||
|
for (int i = length - 1; i >= 0; --i)
|
||||||
|
{
|
||||||
|
out.push_back(static_cast<std::uint8_t>(bits >> (8 * i)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (j.is_number_unsigned() || j.get<std::int64_t>() >= 0)
|
||||||
|
{
|
||||||
|
const auto u = j.get<std::uint64_t>();
|
||||||
|
if (u < 128)
|
||||||
|
{
|
||||||
|
out.push_back(static_cast<std::uint8_t>(u));
|
||||||
|
}
|
||||||
|
else if (u <= 0xFF)
|
||||||
|
{
|
||||||
|
put(0xCC, u, 1);
|
||||||
|
}
|
||||||
|
else if (u <= 0xFFFF)
|
||||||
|
{
|
||||||
|
put(0xCD, u, 2);
|
||||||
|
}
|
||||||
|
else if (u <= 0xFFFFFFFF)
|
||||||
|
{
|
||||||
|
put(0xCE, u, 4);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
put(0xCF, u, 8);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto n = j.get<std::int64_t>();
|
||||||
|
const auto bits = static_cast<std::uint64_t>(n);
|
||||||
|
if (n >= -32)
|
||||||
|
{
|
||||||
|
out.push_back(static_cast<std::uint8_t>(bits));
|
||||||
|
}
|
||||||
|
else if (n >= -128)
|
||||||
|
{
|
||||||
|
put(0xD0, bits, 1);
|
||||||
|
}
|
||||||
|
else if (n >= -32768)
|
||||||
|
{
|
||||||
|
put(0xD1, bits, 2);
|
||||||
|
}
|
||||||
|
else if (n >= -2147483648LL)
|
||||||
|
{
|
||||||
|
put(0xD2, bits, 4);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
put(0xD3, bits, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// integers at every boundary of the CBOR and MessagePack sizes, signed and
|
||||||
|
// unsigned
|
||||||
|
std::vector<json> boundary_integers()
|
||||||
|
{
|
||||||
|
std::vector<json> result;
|
||||||
|
const std::uint64_t boundaries[] =
|
||||||
|
{
|
||||||
|
0, 1, 22, 23, 24, 25, 31, 32, 33, 126, 127, 128, 129, 254, 255, 256, 257,
|
||||||
|
32766, 32767, 32768, 32769, 65534, 65535, 65536, 65537,
|
||||||
|
2147483646ULL, 2147483647ULL, 2147483648ULL, 2147483649ULL,
|
||||||
|
4294967294ULL, 4294967295ULL, 4294967296ULL, 4294967297ULL,
|
||||||
|
9223372036854775806ULL, 9223372036854775807ULL, 9223372036854775808ULL,
|
||||||
|
18446744073709551614ULL, 18446744073709551615ULL
|
||||||
|
};
|
||||||
|
for (const std::uint64_t u : boundaries)
|
||||||
|
{
|
||||||
|
result.emplace_back(u);
|
||||||
|
if (u <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||||
|
{
|
||||||
|
const auto n = static_cast<std::int64_t>(u);
|
||||||
|
result.emplace_back(static_cast<json::number_integer_t>(n));
|
||||||
|
result.emplace_back(static_cast<json::number_integer_t>(-n));
|
||||||
|
result.emplace_back(static_cast<json::number_integer_t>(-n - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Arrays of integers are written in runs straight into space reserved from the
|
||||||
|
// sink, with branch-free encodings; check them against independent encoders for
|
||||||
|
// runs shorter and longer than one reservation, broken up by other values, and
|
||||||
|
// through each sink.
|
||||||
|
TEST_CASE("CBOR and MessagePack integer arrays")
|
||||||
|
{
|
||||||
|
const std::vector<json> integers = boundary_integers();
|
||||||
|
|
||||||
|
for (const std::size_t length :
|
||||||
|
{
|
||||||
|
std::size_t{1}, std::size_t{27}, std::size_t{28}, std::size_t{29}, std::size_t{57}, std::size_t{1000}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for (const std::size_t break_every :
|
||||||
|
{
|
||||||
|
std::size_t{0}, std::size_t{1}, std::size_t{7}, std::size_t{28}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(length)
|
||||||
|
CAPTURE(break_every)
|
||||||
|
|
||||||
|
json array = json::array();
|
||||||
|
std::vector<std::uint8_t> expected_cbor;
|
||||||
|
std::vector<std::uint8_t> expected_msgpack;
|
||||||
|
for (std::size_t i = 0; i < length; ++i)
|
||||||
|
{
|
||||||
|
if (break_every != 0 && i % (break_every + 1) == break_every)
|
||||||
|
{
|
||||||
|
array.push_back("x");
|
||||||
|
expected_cbor.push_back(0x61);
|
||||||
|
expected_cbor.push_back('x');
|
||||||
|
expected_msgpack.push_back(0xA1);
|
||||||
|
expected_msgpack.push_back('x');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const json& value = integers[(i * 7) % integers.size()];
|
||||||
|
array.push_back(value);
|
||||||
|
reference_cbor_integer(expected_cbor, value);
|
||||||
|
reference_msgpack_integer(expected_msgpack, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> head;
|
||||||
|
reference_cbor_head(head, 0x80, length);
|
||||||
|
expected_cbor.insert(expected_cbor.begin(), head.begin(), head.end());
|
||||||
|
|
||||||
|
head.clear();
|
||||||
|
if (length <= 15)
|
||||||
|
{
|
||||||
|
head.push_back(static_cast<std::uint8_t>(0x90 + length));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
head = {0xDC, static_cast<std::uint8_t>(length >> 8), static_cast<std::uint8_t>(length)};
|
||||||
|
}
|
||||||
|
expected_msgpack.insert(expected_msgpack.begin(), head.begin(), head.end());
|
||||||
|
|
||||||
|
CHECK(json::to_cbor(array) == expected_cbor);
|
||||||
|
CHECK(json::to_msgpack(array) == expected_msgpack);
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> through_adapter = {0xAA};
|
||||||
|
json::to_cbor(array, through_adapter);
|
||||||
|
CHECK(std::vector<std::uint8_t>(through_adapter.begin() + 1, through_adapter.end()) == expected_cbor);
|
||||||
|
through_adapter = {0xAA};
|
||||||
|
json::to_msgpack(array, through_adapter);
|
||||||
|
CHECK(std::vector<std::uint8_t>(through_adapter.begin() + 1, through_adapter.end()) == expected_msgpack);
|
||||||
|
|
||||||
|
std::ostringstream stream;
|
||||||
|
json::to_cbor(array, stream);
|
||||||
|
std::string streamed = stream.str();
|
||||||
|
CHECK(std::vector<std::uint8_t>(streamed.begin(), streamed.end()) == expected_cbor);
|
||||||
|
stream.str("");
|
||||||
|
json::to_msgpack(array, stream);
|
||||||
|
streamed = stream.str();
|
||||||
|
CHECK(std::vector<std::uint8_t>(streamed.begin(), streamed.end()) == expected_msgpack);
|
||||||
|
|
||||||
|
CHECK(json::from_cbor(expected_cbor) == array);
|
||||||
|
CHECK(json::from_msgpack(expected_msgpack) == array);
|
||||||
|
|
||||||
|
// the same array as object members
|
||||||
|
const json object = {{"a", array}, {"b", array}};
|
||||||
|
CHECK(json::from_cbor(json::to_cbor(object)) == object);
|
||||||
|
CHECK(json::from_msgpack(json::to_msgpack(object)) == object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// single values, outside an array
|
||||||
|
for (const auto& value : integers)
|
||||||
|
{
|
||||||
|
CAPTURE(value)
|
||||||
|
std::vector<std::uint8_t> expected;
|
||||||
|
reference_cbor_integer(expected, value);
|
||||||
|
CHECK(json::to_cbor(value) == expected);
|
||||||
|
expected.clear();
|
||||||
|
reference_msgpack_integer(expected, value);
|
||||||
|
CHECK(json::to_msgpack(value) == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// binary_reserve_hint() is documented as a *lower* bound on the serialized size,
|
// binary_reserve_hint() is documented as a *lower* bound on the serialized size,
|
||||||
// so that reserving it up front can never leave the returned vector holding
|
// so that reserving it up front can never leave the returned vector holding
|
||||||
// capacity beyond what the value actually needs.
|
// capacity beyond what the value actually needs.
|
||||||
|
|||||||
@@ -938,6 +938,10 @@ TEST_CASE("Eisel-Lemire float conversion")
|
|||||||
const int k = i % 64;
|
const int k = i % 64;
|
||||||
const std::uint64_t x = (std::uint64_t{1} << k) | (a & ((std::uint64_t{1} << k) - 1));
|
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);
|
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"
|
#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 <nlohmann/json.hpp>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -85,3 +92,53 @@ TEST_CASE("binary type whose value type is not std::uint8_t")
|
|||||||
}
|
}
|
||||||
#endif
|
#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 <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <valarray>
|
#include <valarray>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "sax_event_loggers.hpp"
|
#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(json::sax_parse(v, &l));
|
||||||
CHECK(l.events.size() == 1);
|
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
|
// (128) levels deep without recursing; the values they add or replace
|
||||||
// there must still know their parents.
|
// there must still know their parents.
|
||||||
// The values are built rather than parsed, so that the expected messages
|
// 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("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);
|
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>
|
#include <optional>
|
||||||
#elif __has_include(<experimental/optional>)
|
#elif __has_include(<experimental/optional>)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
|
||||||
// for #4804
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
|
||||||
using json_4804 = nlohmann::json::with_binary_t<std::vector<std::byte>>;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_20
|
#ifdef JSON_HAS_CPP_20
|
||||||
@@ -458,49 +453,6 @@ TEST_CASE("regression tests 3")
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
#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")
|
SECTION("issue #5046 - implicit conversion of return json to std::optional no longer implicit")
|
||||||
{
|
{
|
||||||
const json jval{};
|
const json jval{};
|
||||||
|
|||||||
Reference in new issue
Block a user