Compare commits

..
Author SHA1 Message Date
Niels Lohmann 90f85d6d75 Merge branch 'develop' into claude/iterative-diff
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 20:58:06 +02:00
Niels Lohmann 4aaeb01ea4 Merge remote-tracking branch 'origin/develop' into claude/iterative-diff
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 17:16:01 +02:00
Niels Lohmann 2038838eea Copy the diff frame's members instead of holding a reference to it
The loop in diff_iteratively held a reference to the top frame, which
enter() invalidates when it pushes and the end of the loop invalidates
when it pops. Nothing used it afterwards, but a later change could. As in
the other iterative walks, the members the loop reads are now copied out
as constants and the ones it advances are changed through stack.back().
The frame as a whole is not copied: it holds the common keys and the
"add" operations of an object.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 14:00:04 +02:00
Niels Lohmann aa5084d713 Keep diff()'s recursive levels small and its result elided
diff_recursively built every patch operation in place from initializer
lists. Unoptimized builds give each of those temporaries its own stack
slot, so every level of the bounded descent cost kilobytes of stack
(about 6 KB with clang -O0), and the 128 recursive levels overflowed the
1 MB stack of MSVC Debug in the "deeply nested values" test. The
operations and the key comparison of two objects are now built by
separate functions, which diff_iteratively shares, and both diff
functions append to one result instead of returning a patch per level
that the caller copies. With clang -O0, diffing values nested 300 levels
deep now peaks at about 190 KB of stack instead of 880 KB.

Since diff() now owns the only returned value, clang's -Wnrvo no longer
reports the returns of diff_recursively, which alternated between the
local patch and diff_iteratively's result.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-26 07:49:42 +02:00
Niels Lohmann 33c4dfdc18 Note that the diff frame reference is invalidated by pop_back() too
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-26 07:46:43 +02:00
Niels Lohmann b41e43fffc Bound diff()'s descent with a depth count instead of scanning the source
Now that operator== no longer recurses (#5390), diff() can keep its per-level
equality shortcut all the way down. It diffs recursively for the first
detail::recursion_depth_limit() levels, as merge_patch() does, and hands
anything deeper to diff_iteratively(). The nesting_exceeds() scan, which
cost about 30% on equal documents, is gone, and diff() is on par with
develop again.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-25 22:23:57 +02:00
Niels Lohmann 958e0a906b Merge remote-tracking branch 'origin/develop' into claude/iterative-diff 2026-09-25 22:19:57 +02:00
Niels Lohmann 49f038b86a Merge branch 'develop' into claude/iterative-diff
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-25 20:22:45 +02:00
Niels Lohmann 722c2bb561 Merge branch 'develop' into claude/iterative-diff
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-25 18:06:10 +02:00
Niels Lohmann f41296276c Mark the diff frame's value-initialized members for clang-tidy
The braces are kept for GCC's -Weffc++, as in json_sax.hpp.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-25 18:06:10 +02:00
Niels Lohmann 481b8d17fa Merge branch 'develop' into claude/iterative-diff
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-25 08:24:38 +02:00
Niels Lohmann 484f644b86 Diff fewer nesting depths so the test does not time out under Valgrind
Checking every depth up to 300 made test-json_patch exceed the 1500 s ctest
timeout in ci_test_valgrind. Check the depths up to 16, those around the
recursion limit of 128, and 300 instead.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-25 08:21:38 +02:00
Niels Lohmann 08e30eca78 Use the shared recursion limit in diff()
diff_depth_limit() is gone in favor of detail::recursion_depth_limit().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-24 17:12:07 +02:00
Niels Lohmann 582223eb8a Make diff_frame a member struct that declares its special members
GCC's -Weffc++ (an error in CI) asks a class with pointer members, a
user constructor and a non-trivial destructor to declare its copy
constructor and copy assignment; diff_frame's vector and basic_json
members make its destructor non-trivial. Declare all five as defaulted,
which also satisfies clang-tidy's special-member-functions check. Leave
their exception specifications implicit: GCC 4.8 rejects an explicit
one that differs from the implicit one, as it does for flatten_task in
#5517.

The converting constructor cannot throw, and is now declared noexcept
for GCC's -Wnoexcept, which flags the emplace_back() under C++26
otherwise. The struct also moves from diff_iteratively() into the class,
like dump_frame in the serializer.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-24 17:12:07 +02:00
Niels Lohmann c14208a8e0 Diff deeply nested values without recursing per nesting level
diff() descended into both values once per nesting level, and compared
them with operator== on every level on the way, which recurses as well.
Values nested deeply enough - 25,000 levels on an 8 MiB stack - exhausted
the call stack and terminated the process, although parse() accepts
them without complaint. On such a chain the per-level comparisons and
path strings also made diff() quadratic in time and memory.

Both the recursion and operator== only descend as far as the source is
nested. So diff() first checks, recursing at most diff_depth_limit()
(128) levels, whether the source is nested more deeply than that. If not
- all but a vanishing minority of values - the recursive algorithm
diffs it exactly as before, now as diff_recursively(). Otherwise
diff_iteratively() walks the two values on an explicit stack, emitting
the same operations in the same order. It does not compare arrays and
objects with operator== up front (equal ones yield no operations
anyway), keeps the path in one buffer instead of a new string per
level, and hands every subtree that is not nested too deeply back to
diff_recursively(), so equal parts are still skipped quickly.

The check costs one pass over the source. On a 3,000-object document
that is about 30% of diffing two equal values (which is just an
operator== call), about 10% of diffing values that differ in a few
places, and noise when arrays change length. Once operator== no longer
recurses (#5390), the check can go.

Tests check that the patch reproduces the target at every depth up to
300, for json and ordered_json, including reordered members. They also
check the exact operation for a difference deep inside, and diff values
nested 100,000 levels deep.

Fixes #5393 for diff().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-24 17:12:07 +02:00
140 changed files with 1116 additions and 73544 deletions
-81
View File
@@ -1,81 +0,0 @@
name: "Check API documentation"
on:
pull_request:
permissions:
contents: read
jobs:
check_api_docs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout pull request
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install clang
# Used only as a subprocess for `clang++ -E -v` system-include-path discovery in
# extract_api.py; it does not need to version-match the pinned libclang pip wheel
# below, which does the actual AST parsing. Do not "fix" this to be version-matched.
run: sudo apt-get update && sudo apt-get install -y clang
- name: Install Python dependencies
run: pip install -r tools/api_checker/requirements.txt
- name: Extract API and regenerate the committed surface file
run: |
python3 tools/api_checker/extract_api.py \
--header include/nlohmann/json.hpp \
--include include \
--output /tmp/api_snapshot.json \
--surface-output tools/api_checker/api_surface.json
- name: "Check API documentation (Phase 1: advisory)"
# Surfaces missing/broken @sa links without failing the job while the backlog from the
# initial AST-based rollout is burned down. See tools/api_checker/POLICY.md and the PR
# that introduced this workflow for the two-phase rollout plan.
continue-on-error: true
run: |
python3 tools/api_checker/check_docs.py \
--snapshot /tmp/api_snapshot.json
- name: Check macro documentation (advisory only)
# Cross-checks docs/mkdocs/docs/api/macros/ pages against #define sites. Only checks the
# documented-macro-still-exists direction; never blocks CI. See POLICY.md.
run: python3 tools/api_checker/check_macros.py
- name: Check for uncommitted API surface changes
id: diff
run: |
mkdir -p ${{ github.workspace }}/patch
git diff --patch --no-color -- tools/api_checker/api_surface.json > ${{ github.workspace }}/patch/api_surface.patch
if [ -s ${{ github.workspace }}/patch/api_surface.patch ]; then
echo "tools/api_checker/api_surface.json is out of date. Diff:"
cat ${{ github.workspace }}/patch/api_surface.patch
echo "has_diff=true" >> "$GITHUB_OUTPUT"
else
echo "has_diff=false" >> "$GITHUB_OUTPUT"
fi
# Uploaded so contributors can fix their PR with `git apply api_surface.patch`
# instead of installing libclang locally.
- name: Upload patch
if: steps.diff.outputs.has_diff == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: api-surface-patch
path: patch/api_surface.patch
- name: Fail if API surface file is not up to date
# Unlike the doc-backlog check above, this is purely mechanical regeneration with no
# backlog to phase in -- blocking from the start, matching check_amalgamation.yml's
# precedent. Contributors who add/remove/rename public API must regenerate and commit
# tools/api_checker/api_surface.json as part of their PR.
if: steps.diff.outputs.has_diff == 'true'
run: exit 1
-5
View File
@@ -3,7 +3,6 @@
*.gcno *.gcno
*.gcda *.gcda
.DS_Store .DS_Store
__pycache__/
/.idea /.idea
/cmake-build-* /cmake-build-*
@@ -44,9 +43,5 @@ venv
nlohmann_json.spdx nlohmann_json.spdx
# api_checker: ephemeral, location/doc-status-sensitive working file (not the committed
# release-tracking artifact -- see tools/api_checker/api_surface.json for that)
/tools/api_checker/api_snapshot.json
# Bazel-related # Bazel-related
MODULE.bazel.lock MODULE.bazel.lock
+1 -1
View File
@@ -106,7 +106,7 @@ Thanks everyone!
:books: If you want to **learn more** about how to use the library, check out the rest of the [**README**](#examples), have a look at [**code examples**](https://github.com/nlohmann/json/tree/develop/docs/mkdocs/docs/examples), or browse through the [**help pages**](https://json.nlohmann.me). :books: If you want to **learn more** about how to use the library, check out the rest of the [**README**](#examples), have a look at [**code examples**](https://github.com/nlohmann/json/tree/develop/docs/mkdocs/docs/examples), or browse through the [**help pages**](https://json.nlohmann.me).
:construction: If you want to understand the **API** better, check out the [**API Reference**](https://json.nlohmann.me/api/basic_json/) or have a look at the [quick reference](#quick-reference) below. The public API surface is derived mechanically and checked for documentation coverage by the tooling in [`tools/api_checker/`](tools/api_checker/), whose [POLICY.md](tools/api_checker/POLICY.md) defines what counts as public API and what stability is guaranteed. :construction: If you want to understand the **API** better, check out the [**API Reference**](https://json.nlohmann.me/api/basic_json/) or have a look at the [quick reference](#quick-reference) below.
:bug: If you found a **bug**, please check the [**FAQ**](https://json.nlohmann.me/home/faq/) if it is a known issue or the result of a design decision. Please also have a look at the [**issue list**](https://github.com/nlohmann/json/issues) before you [**create a new issue**](https://github.com/nlohmann/json/issues/new/choose). Please provide as much information as possible to help us understand and reproduce your issue. :bug: If you found a **bug**, please check the [**FAQ**](https://json.nlohmann.me/home/faq/) if it is a known issue or the result of a design decision. Please also have a look at the [**issue list**](https://github.com/nlohmann/json/issues) before you [**create a new issue**](https://github.com/nlohmann/json/issues/new/choose). Please provide as much information as possible to help us understand and reproduce your issue.
@@ -420,9 +420,7 @@ basic_json(basic_json&& other) noexcept;
1. Since version 1.0.0. 1. Since version 1.0.0.
2. Since version 1.0.0. 2. Since version 1.0.0.
3. Since version 2.1.0. 3. Since version 2.1.0.
4. Since version 3.2.0. Also initializes the position reported by 4. Since version 3.2.0.
[`start_pos()`](start_pos.md)/[`end_pos()`](end_pos.md) from `val` when
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) is enabled, since version 3.12.0.
5. Since version 1.0.0. 5. Since version 1.0.0.
6. Since version 1.0.0. 6. Since version 1.0.0.
7. Since version 1.0.0. 7. Since version 1.0.0.
@@ -1,38 +0,0 @@
# <small>nlohmann::basic_json::</small>bjdata_version_t
```cpp
enum class bjdata_version_t
{
draft2,
draft3,
};
```
This enumeration is used in the [`to_bjdata`](to_bjdata.md) function to choose which draft version of
the BJData specification to encode ND-array extensions for:
draft2
: encode using the BJData Draft 2 ND-array format
draft3
: encode using the BJData Draft 3 ND-array format
## Examples
??? example
The example shows how `bjdata_version_t` selects the BJData draft used by `to_bjdata`.
```cpp
--8<-- "examples/bjdata_version_t.cpp"
```
Output:
```
--8<-- "examples/bjdata_version_t.output"
```
## Version history
- Added in version 3.12.0.
+2 -2
View File
@@ -80,8 +80,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
the end of the file was not reached when `strict` was set to true the end of the file was not reached when `strict` was set to true
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were - Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from CBOR were
used in the given input or if the input is not valid CBOR used in the given input or if the input is not valid CBOR
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of other - Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string was expected as a map key,
types are not supported, as JSON object keys are always strings) or a string is malformed but not found
## Complexity ## Complexity
@@ -73,8 +73,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
the end of the file was not reached when `strict` was set to true the end of the file was not reached when `strict` was set to true
- Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from - Throws [parse_error.112](../../home/exceptions.md#jsonexceptionparse_error112) if unsupported features from
MessagePack were used in the given input or if the input is not valid MessagePack MessagePack were used in the given input or if the input is not valid MessagePack
- Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a map key is not a string (keys of other - Throws [parse_error.113](../../home/exceptions.md#jsonexceptionparse_error113) if a string was expected as a map key,
types are not supported, as JSON object keys are always strings) or a string is malformed but not found
## Complexity ## Complexity
@@ -1,32 +0,0 @@
# <small>nlohmann::basic_json::</small>initializer_list_t
```cpp
using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>;
```
The type used for the initializer-list [constructor](basic_json.md) (overload 5) and for functions
such as [`operator=`](operator=.md) that accept a braced-init-list of JSON values. Each element wraps a
`basic_json` value or something convertible to one, deferring the decision of whether the list should be
parsed as a JSON array or a JSON object to the constructor itself.
See the [constructor](basic_json.md) documentation for how `initializer_list_t` values are interpreted.
## Examples
??? example
The example shows how an `initializer_list_t` is used to construct a JSON value.
```cpp
--8<-- "examples/initializer_list_t.cpp"
```
Output:
```
--8<-- "examples/initializer_list_t.output"
```
## Version history
- Since version 1.0.0.
@@ -1,31 +0,0 @@
# <small>nlohmann::basic_json::</small>json_sax_t
```cpp
using json_sax_t = json_sax<basic_json>;
```
The [`json_sax`](../json_sax/index.md) interface bound to this `basic_json` specialization, i.e. with
`BasicJsonType` fixed to `basic_json`. Used as the SAX interface type by [`sax_parse`](sax_parse.md) and
other SAX-based parsing functions.
See [`nlohmann::json_sax`](../json_sax/index.md) for more information.
## Examples
??? example
The example shows the type `json_sax_t`.
```cpp
--8<-- "examples/json_sax_t.cpp"
```
Output:
```
--8<-- "examples/json_sax_t.output"
```
## Version history
- Added in version 3.2.0.
@@ -51,5 +51,3 @@ Linear.
## Version history ## Version history
- Added in version 1.0.0. - Added in version 1.0.0.
- The `noexcept` specification was extended to also depend on
[`json_base_class_t`](json_base_class_t.md)'s move-assignment in version 3.11.3.
@@ -85,8 +85,3 @@ Linear in the size of the JSON value.
- Since version 1.0.0. - Since version 1.0.0.
- Macros `JSON_EXPLICIT`/[`JSON_USE_IMPLICIT_CONVERSIONS`](../macros/json_use_implicit_conversions.md) added - Macros `JSON_EXPLICIT`/[`JSON_USE_IMPLICIT_CONVERSIONS`](../macros/json_use_implicit_conversions.md) added
in version 3.9.0. in version 3.9.0.
- The exclusion of `std::any` from this conversion became conditional on
[`JSON_HAS_STATIC_RTTI`](../macros/json_has_static_rtti.md) in version 3.11.3.
- `std::optional<T>` excluded from this conversion in version 3.13.0; use
[`get<std::optional<T>>()`](get.md)/[`get_to()`](get_to.md) instead (see
[Converting values](../../features/conversions.md)).
@@ -1,32 +0,0 @@
# <small>nlohmann::byte_container_with_subtype::</small>container_type
```cpp
using container_type = BinaryType;
```
The type of the underlying binary container, forwarded from the `BinaryType` template parameter that
`byte_container_with_subtype` is instantiated with. `byte_container_with_subtype` publicly inherits from
`container_type`.
See [`basic_json::binary_t`](../basic_json/binary_t.md) for the type typically used to instantiate
`BinaryType`.
## Examples
??? example
The example shows the type `container_type`.
```cpp
--8<-- "examples/byte_container_with_subtype__container_type.cpp"
```
Output:
```
--8<-- "examples/byte_container_with_subtype__container_type.output"
```
## Version history
- Since version 3.8.0.
@@ -1,45 +0,0 @@
# <small>nlohmann::byte_container_with_subtype::</small>operator==
```cpp
bool operator==(const byte_container_with_subtype& rhs) const;
```
Compares two `byte_container_with_subtype` values for equality by comparing the underlying binary
container, the subtype, and whether a subtype is set.
## Parameters
`rhs` (in)
: value to compare `*this` against
## Return value
whether `*this` and `rhs` are equal
## Exception safety
No-throw guarantee: this function never throws exceptions.
## Complexity
Linear in the size of the underlying binary container.
## Examples
??? example
The example demonstrates comparing `byte_container_with_subtype` values.
```cpp
--8<-- "examples/byte_container_with_subtype__operator_eq.cpp"
```
Output:
```
--8<-- "examples/byte_container_with_subtype__operator_eq.output"
```
## Version history
- Since version 3.8.0.
@@ -1,45 +0,0 @@
# <small>nlohmann::byte_container_with_subtype::</small>operator!=
```cpp
bool operator!=(const byte_container_with_subtype& rhs) const;
```
Compares two `byte_container_with_subtype` values for inequality. Implemented as the negation of
[`operator==`](operator_eq.md).
## Parameters
`rhs` (in)
: value to compare `*this` against
## Return value
whether `*this` and `rhs` are not equal
## Exception safety
No-throw guarantee: this function never throws exceptions.
## Complexity
Linear in the size of the underlying binary container.
## Examples
??? example
The example demonstrates comparing `byte_container_with_subtype` values.
```cpp
--8<-- "examples/byte_container_with_subtype__operator_ne.cpp"
```
Output:
```
--8<-- "examples/byte_container_with_subtype__operator_ne.output"
```
## Version history
- Since version 3.8.0.
@@ -1,28 +0,0 @@
# <small>nlohmann::byte_container_with_subtype::</small>subtype_type
```cpp
using subtype_type = std::uint64_t;
```
The type used to store the optional binary subtype tag. See [`subtype`](subtype.md) and
[`set_subtype`](set_subtype.md).
## Examples
??? example
The example shows the type `subtype_type`.
```cpp
--8<-- "examples/byte_container_with_subtype__subtype_type.cpp"
```
Output:
```
--8<-- "examples/byte_container_with_subtype__subtype_type.output"
```
## Version history
- Since version 3.8.0.
-30
View File
@@ -1,30 +0,0 @@
# <small>nlohmann::json_sax::</small>binary_t
```cpp
using binary_t = typename BasicJsonType::binary_t;
```
The type used by the [`binary`](binary.md) callback for JSON binary values, forwarded from the
`BasicJsonType` template parameter.
See [`basic_json::binary_t`](../basic_json/binary_t.md) for more information.
## Examples
??? example
The example shows the type `binary_t` and its relation to `basic_json::binary_t`.
```cpp
--8<-- "examples/json_sax__binary_t.cpp"
```
Output:
```
--8<-- "examples/json_sax__binary_t.output"
```
## Version history
- Added in version 3.8.0.
-33
View File
@@ -1,33 +0,0 @@
# <small>nlohmann::json_sax::</small>json_sax
```cpp
// (1)
json_sax() = default;
// (2)
json_sax(const json_sax&) = default;
// (3)
json_sax(json_sax&&) noexcept = default;
```
1. Default constructor.
2. Copy constructor.
3. Move constructor.
`json_sax` is a pure abstract base class with no data members of its own, so all three constructors are
defaulted and only exist to make derived SAX consumers explicitly copyable/movable.
## Exception safety
No-throw guarantee: none of these constructors throw exceptions.
## Complexity
Constant.
<!-- NOLINT Examples -->
## Version history
- Added in version 3.2.0.
@@ -1,30 +0,0 @@
# <small>nlohmann::json_sax::</small>number_float_t
```cpp
using number_float_t = typename BasicJsonType::number_float_t;
```
The type used by the [`number_float`](number_float.md) callback for JSON floating-point numbers,
forwarded from the `BasicJsonType` template parameter.
See [`basic_json::number_float_t`](../basic_json/number_float_t.md) for more information.
## Examples
??? example
The example shows the type `number_float_t` and its relation to `basic_json::number_float_t`.
```cpp
--8<-- "examples/json_sax__number_float_t.cpp"
```
Output:
```
--8<-- "examples/json_sax__number_float_t.output"
```
## Version history
- Added in version 3.2.0.
@@ -1,30 +0,0 @@
# <small>nlohmann::json_sax::</small>number_integer_t
```cpp
using number_integer_t = typename BasicJsonType::number_integer_t;
```
The type used by the [`number_integer`](number_integer.md) callback for JSON integer numbers, forwarded
from the `BasicJsonType` template parameter.
See [`basic_json::number_integer_t`](../basic_json/number_integer_t.md) for more information.
## Examples
??? example
The example shows the type `number_integer_t` and its relation to `basic_json::number_integer_t`.
```cpp
--8<-- "examples/json_sax__number_integer_t.cpp"
```
Output:
```
--8<-- "examples/json_sax__number_integer_t.output"
```
## Version history
- Added in version 3.2.0.
@@ -1,30 +0,0 @@
# <small>nlohmann::json_sax::</small>number_unsigned_t
```cpp
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
```
The type used by the [`number_unsigned`](number_unsigned.md) callback for JSON unsigned integer numbers,
forwarded from the `BasicJsonType` template parameter.
See [`basic_json::number_unsigned_t`](../basic_json/number_unsigned_t.md) for more information.
## Examples
??? example
The example shows the type `number_unsigned_t` and its relation to `basic_json::number_unsigned_t`.
```cpp
--8<-- "examples/json_sax__number_unsigned_t.cpp"
```
Output:
```
--8<-- "examples/json_sax__number_unsigned_t.output"
```
## Version history
- Added in version 3.2.0.
@@ -1,29 +0,0 @@
# <small>nlohmann::json_sax::</small>operator=
```cpp
// (1)
json_sax& operator=(const json_sax&) = default;
// (2)
json_sax& operator=(json_sax&&) noexcept = default;
```
1. Copy assignment operator.
2. Move assignment operator.
`json_sax` is a pure abstract base class with no data members of its own, so both assignment operators
are defaulted and only exist to make derived SAX consumers explicitly copy-/move-assignable.
## Exception safety
No-throw guarantee: neither operator throws exceptions.
## Complexity
Constant.
<!-- NOLINT Examples -->
## Version history
- Added in version 3.2.0.
-30
View File
@@ -1,30 +0,0 @@
# <small>nlohmann::json_sax::</small>string_t
```cpp
using string_t = typename BasicJsonType::string_t;
```
The type used by the [`string`](string.md) and [`key`](key.md) callbacks for JSON strings and object
keys, forwarded from the `BasicJsonType` template parameter.
See [`basic_json::string_t`](../basic_json/string_t.md) for more information.
## Examples
??? example
The example shows the type `string_t` and its relation to `basic_json::string_t`.
```cpp
--8<-- "examples/json_sax__string_t.cpp"
```
Output:
```
--8<-- "examples/json_sax__string_t.output"
```
## Version history
- Added in version 3.2.0.
@@ -1,22 +0,0 @@
# <small>nlohmann::json_sax::</small>~json_sax
```cpp
virtual ~json_sax() = default;
```
Destructor. Virtual to allow proper destruction of derived SAX consumer classes through a
pointer/reference to `json_sax`.
## Exception safety
No-throw guarantee: this destructor never throws exceptions.
## Complexity
Constant.
<!-- NOLINT Examples -->
## Version history
- Added in version 3.2.0.
+4 -4
View File
@@ -8,16 +8,16 @@ This type preserves the insertion order of object keys.
## Iterator invalidation ## Iterator invalidation
The type is based on [`ordered_map`](ordered_map/index.md) which in turn uses a `std::vector` to store object elements. The type is based on [`ordered_map`](ordered_map.md) which in turn uses a `std::vector` to store object elements.
Therefore, adding object elements can yield a reallocation in which case all iterators (including the Therefore, adding object elements can yield a reallocation in which case all iterators (including the
[`end()`](basic_json/end.md) iterator) and all references to the elements are invalidated. Also, any iterator or [`end()`](basic_json/end.md) iterator) and all references to the elements are invalidated. Also, any iterator or
reference after the insertion point will point to the same index, which is now a different value. reference after the insertion point will point to the same index, which is now a different value.
## Complexity ## Complexity
[`ordered_map`](ordered_map/index.md) has no lookup index: every key-based object operation is a linear scan, so building or [`ordered_map`](ordered_map.md) has no lookup index: every key-based object operation is a linear scan, so building or
parsing an object of `n` keys costs O(n²) rather than O(n log n). See parsing an object of `n` keys costs O(n²) rather than O(n log n). See
[`ordered_map` complexity](ordered_map/index.md#complexity) for the per-operation table and for measured numbers. [`ordered_map` complexity](ordered_map.md#complexity) for the per-operation table and for measured numbers.
## Examples ## Examples
@@ -37,7 +37,7 @@ parsing an object of `n` keys costs O(n²) rather than O(n log n). See
## See also ## See also
- [ordered_map](ordered_map/index.md) - [ordered_map](ordered_map.md)
- [Object Order](../features/object_order.md) - [Object Order](../features/object_order.md)
## Version history ## Version history
@@ -6,7 +6,7 @@ template<class Key, class T, class IgnoredLess = std::less<Key>,
struct ordered_map : std::vector<std::pair<const Key, T>, Allocator>; struct ordered_map : std::vector<std::pair<const Key, T>, Allocator>;
``` ```
A minimal map-like container that preserves insertion order for use within [`nlohmann::ordered_json`](../ordered_json.md) A minimal map-like container that preserves insertion order for use within [`nlohmann::ordered_json`](ordered_json.md)
(`nlohmann::basic_json<ordered_map>`). (`nlohmann::basic_json<ordered_map>`).
## Template parameters ## Template parameters
@@ -32,12 +32,12 @@ case all iterators (including the `end()` iterator) and all references to the el
- **key_type** - key type (`Key`) - **key_type** - key type (`Key`)
- **mapped_type** - mapped type (`T`) - **mapped_type** - mapped type (`T`)
- [**Container**](Container.md) - base container type (`#!cpp std::vector<std::pair<const Key, T>, Allocator>`) - **Container** - base container type (`#!cpp std::vector<std::pair<const Key, T>, Allocator>`)
- **iterator** - **iterator**
- **const_iterator** - **const_iterator**
- **size_type** - **size_type**
- **value_type** - **value_type**
- [**key_compare**](key_compare.md) - key comparison function - **key_compare** - key comparison function
```cpp ```cpp
std::equal_to<Key> // until C++14 std::equal_to<Key> // until C++14
@@ -46,16 +46,15 @@ std::equal_to<> // since C++14
## Member functions ## Member functions
- [(constructor)](ordered_map.md) - (constructor)
- [(destructor)](~ordered_map.md) - (destructor)
- [**operator=**](operator=.md) - **emplace**
- [**emplace**](emplace.md) - **operator\[\]**
- [**operator\[\]**](operator[].md) - **at**
- [**at**](at.md) - **erase**
- [**erase**](erase.md) - **count**
- [**count**](count.md) - **find**
- [**find**](find.md) - **insert**
- [**insert**](insert.md)
## Complexity ## Complexity
@@ -117,9 +116,9 @@ This differs from `#!cpp std::map`, where the same operations are O(log n).
## See also ## See also
- [ordered_json](../ordered_json.md) - [ordered_json](ordered_json.md)
## Version history ## Version history
- Added in version 3.9.0 to implement [`nlohmann::ordered_json`](../ordered_json.md). - Added in version 3.9.0 to implement [`nlohmann::ordered_json`](ordered_json.md).
- Added **key_compare** member in version 3.11.0. - Added **key_compare** member in version 3.11.0.
@@ -1,28 +0,0 @@
# <small>nlohmann::ordered_map::</small>Container
```cpp
using Container = std::vector<std::pair<const Key, T>, Allocator>;
```
The base container type that `ordered_map` publicly inherits from. Elements are stored in insertion
order as `#!cpp std::pair<const Key, T>` entries in a `std::vector`.
## Examples
??? example
The example shows the type `Container`.
```cpp
--8<-- "examples/ordered_map__Container.cpp"
```
Output:
```
--8<-- "examples/ordered_map__Container.output"
```
## Version history
- Added in version 3.9.0 to implement [`nlohmann::ordered_json`](../ordered_json.md).
-61
View File
@@ -1,61 +0,0 @@
# <small>nlohmann::ordered_map::</small>at
```cpp
// (1)
T& at(const key_type& key);
const T& at(const key_type& key) const;
// (2)
template<class KeyType>
T& at(KeyType&& key);
template<class KeyType>
const T& at(KeyType&& key) const;
```
1. Returns a reference to the value mapped to `key`.
2. Same as (1), but for any `KeyType` comparable to `key_type` via [`key_compare`](key_compare.md)
(heterogeneous lookup, e.g. looking up by a `#!cpp const char*` without constructing a temporary
`key_type`). Only participates in overload resolution if `KeyType` is usable as a key type.
## Template parameters
`KeyType`
: a type comparable to `key_type` via [`key_compare`](key_compare.md)
## Parameters
`key` (in)
: key of the element to find
## Return value
reference to the mapped value of the element with key equal to `key`
## Exceptions
Throws `std::out_of_range` if no element with key `key` exists.
## Complexity
Linear in the number of elements.
## Examples
??? example
The example shows how `at` is used.
```cpp
--8<-- "examples/ordered_map__at.cpp"
```
Output:
```
--8<-- "examples/ordered_map__at.output"
```
## Version history
- Added in version 3.9.1 to implement [`nlohmann::ordered_json`](../ordered_json.md).
- Overload (2) added in version 3.11.0.
-53
View File
@@ -1,53 +0,0 @@
# <small>nlohmann::ordered_map::</small>count
```cpp
// (1)
size_type count(const key_type& key) const;
// (2)
template<class KeyType>
size_type count(KeyType&& key) const;
```
1. Returns the number of elements with key equal to `key` (0 or 1, since keys are unique).
2. Same as (1), but for any `KeyType` comparable to `key_type` via [`key_compare`](key_compare.md)
(heterogeneous lookup). Only participates in overload resolution if `KeyType` is usable as a key type.
## Template parameters
`KeyType`
: a type comparable to `key_type` via [`key_compare`](key_compare.md)
## Parameters
`key` (in)
: key of the elements to count
## Return value
number of elements with key equal to `key` (0 or 1)
## Complexity
Linear in the number of elements.
## Examples
??? example
The example shows how `count` is used.
```cpp
--8<-- "examples/ordered_map__count.cpp"
```
Output:
```
--8<-- "examples/ordered_map__count.output"
```
## Version history
- Added in version 3.9.1 to implement [`nlohmann::ordered_json`](../ordered_json.md).
- Overload (2) added in version 3.11.0.
@@ -1,58 +0,0 @@
# <small>nlohmann::ordered_map::</small>emplace
```cpp
// (1)
std::pair<iterator, bool> emplace(const key_type& key, T&& t);
// (2)
template<class KeyType>
std::pair<iterator, bool> emplace(KeyType&& key, T&& t);
```
1. Inserts `#!cpp {key, t}` if no element with an equal key already exists (per [`key_compare`](key_compare.md)),
appending it at the end to preserve insertion order. If an equal key already exists, does nothing.
2. Same as (1), but for any `KeyType` comparable to `key_type` via [`key_compare`](key_compare.md)
(heterogeneous lookup). Only participates in overload resolution if `KeyType` is usable as a key type.
## Template parameters
`KeyType`
: a type comparable to `key_type` via [`key_compare`](key_compare.md)
## Parameters
`key` (in)
: key of the element to insert
`t` (in)
: value of the element to insert
## Return value
pair of an iterator to the (possibly newly inserted) element, and a `bool` that is `true` if insertion
took place and `false` if an element with an equal key already existed
## Complexity
Linear in the number of elements.
## Examples
??? example
The example shows how `emplace` is used.
```cpp
--8<-- "examples/ordered_map__emplace.cpp"
```
Output:
```
--8<-- "examples/ordered_map__emplace.output"
```
## Version history
- Added in version 3.9.0 to implement [`nlohmann::ordered_json`](../ordered_json.md).
- Overload (2) added in version 3.11.0.
-75
View File
@@ -1,75 +0,0 @@
# <small>nlohmann::ordered_map::</small>erase
```cpp
// (1)
size_type erase(const key_type& key);
// (2)
template<class KeyType>
size_type erase(KeyType&& key);
// (3)
iterator erase(iterator pos);
// (4)
iterator erase(iterator first, iterator last);
```
1. Removes the element with key equal to `key`, if any, preserving the relative order of the remaining
elements.
2. Same as (1), but for any `KeyType` comparable to `key_type` via [`key_compare`](key_compare.md)
(heterogeneous lookup). Only participates in overload resolution if `KeyType` is usable as a key type.
3. Removes the element at `pos`.
4. Removes the elements in range `[first, last)`.
## Template parameters
`KeyType`
: a type comparable to `key_type` via [`key_compare`](key_compare.md)
## Parameters
`key` (in)
: key of the element to remove
`pos` (in)
: iterator to the element to remove
`first` (in)
: iterator to the first element to remove
`last` (in)
: iterator one past the last element to remove
## Return value
1. number of elements removed (0 or 1)
2. number of elements removed (0 or 1)
3. iterator following the removed element
4. iterator following the last removed element
## Complexity
Linear in the number of elements (elements after the removed one(s) are shifted to keep storage
contiguous).
## Examples
??? example
The example shows how `erase` is used.
```cpp
--8<-- "examples/ordered_map__erase.cpp"
```
Output:
```
--8<-- "examples/ordered_map__erase.output"
```
## Version history
- Added in version 3.9.0 to implement [`nlohmann::ordered_json`](../ordered_json.md).
- Overload (2) added in version 3.11.0.
-56
View File
@@ -1,56 +0,0 @@
# <small>nlohmann::ordered_map::</small>find
```cpp
// (1)
iterator find(const key_type& key);
const_iterator find(const key_type& key) const;
// (2)
template<class KeyType>
iterator find(KeyType&& key);
template<class KeyType>
const_iterator find(KeyType&& key) const;
```
1. Returns an iterator to the element with key equal to `key`, or `end()` if no such element exists.
2. Same as (1), but for any `KeyType` comparable to `key_type` via [`key_compare`](key_compare.md)
(heterogeneous lookup). Only participates in overload resolution if `KeyType` is usable as a key type.
## Template parameters
`KeyType`
: a type comparable to `key_type` via [`key_compare`](key_compare.md)
## Parameters
`key` (in)
: key of the element to find
## Return value
iterator to the element with key equal to `key`, or `end()` if not found
## Complexity
Linear in the number of elements.
## Examples
??? example
The example shows how `find` is used.
```cpp
--8<-- "examples/ordered_map__find.cpp"
```
Output:
```
--8<-- "examples/ordered_map__find.output"
```
## Version history
- Added in version 3.9.1 to implement [`nlohmann::ordered_json`](../ordered_json.md).
- Overload (2) added in version 3.11.0.
@@ -1,63 +0,0 @@
# <small>nlohmann::ordered_map::</small>insert
```cpp
// (1)
std::pair<iterator, bool> insert(value_type&& value);
std::pair<iterator, bool> insert(const value_type& value);
// (2)
template<typename InputIt>
void insert(InputIt first, InputIt last);
```
1. Inserts `value` if no element with an equal key already exists (per [`key_compare`](key_compare.md)),
appending it at the end to preserve insertion order. If an equal key already exists, does nothing.
2. Inserts the elements from range `[first, last)`, in iteration order, applying the same equal-key rule
as (1) to each element.
## Template parameters
`InputIt`
: an input iterator type
## Parameters
`value` (in)
: value to insert
`first` (in)
: iterator to the first element to insert
`last` (in)
: iterator one past the last element to insert
## Return value
1. pair of an iterator to the (possibly newly inserted) element, and a `bool` that is `true` if insertion
took place and `false` if an element with an equal key already existed
2. (none)
## Complexity
1. Linear in the number of elements.
2. Linear in the distance between `first` and `last`, times linear in the number of elements.
## Examples
??? example
The example shows how `insert` is used.
```cpp
--8<-- "examples/ordered_map__insert.cpp"
```
Output:
```
--8<-- "examples/ordered_map__insert.output"
```
## Version history
- Added in version 3.9.1 to implement [`nlohmann::ordered_json`](../ordered_json.md).
@@ -1,34 +0,0 @@
# <small>nlohmann::ordered_map::</small>key_compare
```cpp
using key_compare = std::equal_to<Key>; // until C++14
using key_compare = std::equal_to<>; // since C++14
```
The comparator used to determine key equality when looking up elements. Unlike `std::map`, `ordered_map`
uses linear search with `key_compare` rather than an ordering relation, since element order reflects
insertion order rather than key order.
Since C++14, the transparent `#!cpp std::equal_to<>` is used, which enables heterogeneous lookup (e.g.
looking up by a `#!cpp const char*` key without constructing a temporary `Key`).
## Examples
??? example
The example shows how `key_compare` is used.
```cpp
--8<-- "examples/ordered_map__key_compare.cpp"
```
Output:
```
--8<-- "examples/ordered_map__key_compare.output"
```
## Version history
- Added in version 3.11.0.
@@ -1,32 +0,0 @@
# <small>nlohmann::ordered_map::</small>operator=
```cpp
// (1)
ordered_map& operator=(const ordered_map& other);
// (2)
ordered_map& operator=(ordered_map&& other) noexcept(std::is_nothrow_move_assignable<Container>::value);
```
1. Copy assignment operator.
2. Move assignment operator.
## Parameters
`other` (in)
: value to assign from
## Return value
`*this`
## Complexity
1. Linear in the size of `other`.
2. Constant.
<!-- NOLINT Examples -->
## Version history
- Added in version 3.9.0 to implement [`nlohmann::ordered_json`](../ordered_json.md).
@@ -1,62 +0,0 @@
# <small>nlohmann::ordered_map::</small>operator[]
```cpp
// (1)
T& operator[](const key_type& key);
const T& operator[](const key_type& key) const;
// (2)
template<class KeyType>
T& operator[](KeyType&& key);
template<class KeyType>
const T& operator[](KeyType&& key) const;
```
1. Returns a reference to the value mapped to `key`, inserting a default-constructed `T` (non-`const`
overload only) if no such element exists yet.
2. Same as (1), but for any `KeyType` comparable to `key_type` via [`key_compare`](key_compare.md)
(heterogeneous lookup). Only participates in overload resolution if `KeyType` is usable as a key type.
## Template parameters
`KeyType`
: a type comparable to `key_type` via [`key_compare`](key_compare.md)
## Parameters
`key` (in)
: key of the element to find or insert
## Return value
reference to the mapped value of the element with key equal to `key`
## Exceptions
The `const` overloads throw `std::out_of_range` if no element with key `key` exists (they delegate to
[`at`](at.md)).
## Complexity
Linear in the number of elements.
## Examples
??? example
The example shows how `operator[]` is used.
```cpp
--8<-- "examples/ordered_map__operator_idx.cpp"
```
Output:
```
--8<-- "examples/ordered_map__operator_idx.output"
```
## Version history
- Added in version 3.9.0 to implement [`nlohmann::ordered_json`](../ordered_json.md).
- Overload (2) added in version 3.11.0.
@@ -1,66 +0,0 @@
# <small>nlohmann::ordered_map::</small>ordered_map
```cpp
// (1)
ordered_map() noexcept(noexcept(Container()));
// (2)
explicit ordered_map(const Allocator& alloc) noexcept(noexcept(Container(alloc)));
// (3)
template <class It>
ordered_map(It first, It last, const Allocator& alloc = Allocator());
// (4)
ordered_map(std::initializer_list<value_type> init, const Allocator& alloc = Allocator());
// (5)
ordered_map(const ordered_map&) = default;
// (6)
ordered_map(ordered_map&&) noexcept(std::is_nothrow_move_constructible<Container>::value) = default;
```
1. Default constructor. Creates an empty `ordered_map`.
2. Creates an empty `ordered_map` using the given allocator.
3. Creates an `ordered_map` from the elements in range `[first, last)`, inserted in iteration order.
4. Creates an `ordered_map` from an initializer list of key/value pairs, inserted in list order.
5. Copy constructor.
6. Move constructor.
These constructors are declared explicitly (rather than inherited via `#!cpp using Container::Container`)
because older compilers (GCC <= 5.5, Xcode <= 9.4) do not handle the inherited constructors correctly.
## Template parameters
`It`
: an input iterator type
## Parameters
`alloc` (in)
: allocator to use for the underlying container
`first` (in)
: iterator to the first element to insert
`last` (in)
: iterator one past the last element to insert
`init` (in)
: initializer list of key/value pairs to insert
## Complexity
1. Constant.
2. Constant.
3. Linear in the distance between `first` and `last`.
4. Linear in the size of `init`.
5. Linear in the size of `other`.
6. Constant.
<!-- NOLINT Examples -->
## Version history
- Added in version 3.9.0 to implement [`nlohmann::ordered_json`](../ordered_json.md).
@@ -1,17 +0,0 @@
# <small>nlohmann::ordered_map::</small>~ordered_map
```cpp
~ordered_map() = default;
```
Destroys the `ordered_map` and frees all allocated memory.
## Complexity
Linear in the number of elements.
<!-- NOLINT Examples -->
## Version history
- Added in version 3.9.0 to implement [`nlohmann::ordered_json`](../ordered_json.md).
@@ -1,21 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
// an empty binary value is encoded differently by the two drafts:
// draft2 omits the optimized type marker for an empty byte array,
// while draft3 always writes it
json j = json::binary({});
// encode using BJData draft2 (the default)
auto v_draft2 = json::to_bjdata(j, true, true, json::bjdata_version_t::draft2);
// encode using BJData draft3
auto v_draft3 = json::to_bjdata(j, true, true, json::bjdata_version_t::draft3);
std::cout << "draft2 size: " << v_draft2.size() << '\n'
<< "draft3 size: " << v_draft3.size() << std::endl;
}
@@ -1,2 +0,0 @@
draft2 size: 4
draft3 size: 6
@@ -1,11 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
using byte_container_with_subtype = nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>>;
int main()
{
std::cout << std::boolalpha
<< std::is_same<byte_container_with_subtype::container_type, std::vector<std::uint8_t>>::value
<< std::endl;
}
@@ -1,15 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
using byte_container_with_subtype = nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>>;
int main()
{
byte_container_with_subtype c1({0xca, 0xfe});
byte_container_with_subtype c2({0xca, 0xfe});
byte_container_with_subtype c3({0xca, 0xfe}, 42);
std::cout << std::boolalpha
<< "c1 == c2: " << (c1 == c2) << '\n'
<< "c1 == c3: " << (c1 == c3) << std::endl;
}
@@ -1,2 +0,0 @@
c1 == c2: true
c1 == c3: false
@@ -1,15 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
using byte_container_with_subtype = nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>>;
int main()
{
byte_container_with_subtype c1({0xca, 0xfe});
byte_container_with_subtype c2({0xca, 0xfe});
byte_container_with_subtype c3({0xca, 0xfe}, 42);
std::cout << std::boolalpha
<< "c1 != c2: " << (c1 != c2) << '\n'
<< "c1 != c3: " << (c1 != c3) << std::endl;
}
@@ -1,2 +0,0 @@
c1 != c2: false
c1 != c3: true
@@ -1,10 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
using byte_container_with_subtype = nlohmann::byte_container_with_subtype<std::vector<std::uint8_t>>;
int main()
{
std::cout << std::boolalpha
<< std::is_same<byte_container_with_subtype::subtype_type, std::uint64_t>::value << std::endl;
}
@@ -1,13 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
// an initializer_list_t is what a braced-init-list of JSON values is deduced as
json::initializer_list_t init = {"a", 1, 2.0, false};
json j(init);
std::cout << j.dump() << std::endl;
}
@@ -1 +0,0 @@
["a",1,2.0,false]
@@ -1,10 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
std::cout << std::boolalpha
<< std::is_same<json::json_sax_t::binary_t, json::binary_t>::value << std::endl;
}
@@ -1 +0,0 @@
true
@@ -1,10 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
std::cout << std::boolalpha
<< std::is_same<json::json_sax_t::number_float_t, json::number_float_t>::value << std::endl;
}
@@ -1 +0,0 @@
true
@@ -1,10 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
std::cout << std::boolalpha
<< std::is_same<json::json_sax_t::number_integer_t, json::number_integer_t>::value << std::endl;
}
@@ -1 +0,0 @@
true
@@ -1,10 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
std::cout << std::boolalpha
<< std::is_same<json::json_sax_t::number_unsigned_t, json::number_unsigned_t>::value << std::endl;
}
@@ -1 +0,0 @@
true
@@ -1,10 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
std::cout << std::boolalpha
<< std::is_same<json::json_sax_t::string_t, json::string_t>::value << std::endl;
}
@@ -1 +0,0 @@
true
-10
View File
@@ -1,10 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
std::cout << std::boolalpha
<< std::is_same<json::json_sax_t, nlohmann::json_sax<json>>::value << std::endl;
}
@@ -1 +0,0 @@
true
@@ -1,12 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
int main()
{
using Map = nlohmann::ordered_map<std::string, int>;
std::cout << std::boolalpha
<< "Container is std::vector<std::pair<const Key, T>>: "
<< std::is_same<Map::Container, std::vector<std::pair<const std::string, int>>>::value
<< std::endl;
}
@@ -1 +0,0 @@
Container is std::vector<std::pair<const Key, T>>: true
@@ -1,26 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
int main()
{
nlohmann::ordered_map<std::string, int> m;
m["one"] = 1;
m["two"] = 2;
// access an existing element
std::cout << "m.at(\"one\") = " << m.at("one") << std::endl;
// modify through the reference returned by at()
m.at("two") = 22;
std::cout << "m.at(\"two\") = " << m.at("two") << std::endl;
// accessing a missing key throws
try
{
m.at("three");
}
catch (const std::out_of_range& e)
{
std::cout << "exception: " << e.what() << std::endl;
}
}
@@ -1,3 +0,0 @@
m.at("one") = 1
m.at("two") = 22
exception: key not found
@@ -1,12 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
int main()
{
nlohmann::ordered_map<std::string, int> m;
m["one"] = 1;
std::cout << std::boolalpha
<< "m.count(\"one\") = " << m.count("one") << '\n'
<< "m.count(\"two\") = " << m.count("two") << std::endl;
}
@@ -1,2 +0,0 @@
m.count("one") = 1
m.count("two") = 0
@@ -1,15 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
int main()
{
nlohmann::ordered_map<std::string, std::string> m;
// emplace a new element
auto res1 = m.emplace("one", "eins");
std::cout << std::boolalpha << "inserted: " << res1.second << ", value: " << res1.first->second << std::endl;
// emplace with an already-existing key: no-op, returns the existing element
auto res2 = m.emplace("one", "uno");
std::cout << std::boolalpha << "inserted: " << res2.second << ", value: " << res2.first->second << std::endl;
}
@@ -1,2 +0,0 @@
inserted: true, value: eins
inserted: false, value: eins
@@ -1,24 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
int main()
{
nlohmann::ordered_map<std::string, int> m;
m["one"] = 1;
m["two"] = 2;
m["three"] = 3;
// erase by key
std::size_t removed = m.erase("two");
std::cout << "removed by key: " << removed << std::endl;
// erase by iterator
m.erase(m.begin());
std::cout << "remaining: ";
for (const auto& element : m)
{
std::cout << element.first << ' ';
}
std::cout << std::endl;
}
@@ -1,2 +0,0 @@
removed by key: 1
remaining: three
@@ -1,19 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
int main()
{
nlohmann::ordered_map<std::string, int> m;
m["one"] = 1;
auto it = m.find("one");
if (it != m.end())
{
std::cout << "found: " << it->first << " = " << it->second << std::endl;
}
if (m.find("two") == m.end())
{
std::cout << "\"two\" not found" << std::endl;
}
}
@@ -1,2 +0,0 @@
found: one = 1
"two" not found
@@ -1,21 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
int main()
{
nlohmann::ordered_map<std::string, int> m;
// insert a single value
auto res = m.insert({"one", 1});
std::cout << std::boolalpha << "inserted: " << res.second << std::endl;
// insert a range from another container
std::vector<std::pair<const std::string, int>> more = {{"two", 2}, {"three", 3}};
m.insert(more.begin(), more.end());
for (const auto& element : m)
{
std::cout << element.first << ':' << element.second << ' ';
}
std::cout << std::endl;
}
@@ -1,2 +0,0 @@
inserted: true
one:1 two:2 three:3
@@ -1,12 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
int main()
{
using Map = nlohmann::ordered_map<std::string, int>;
Map::key_compare compare{};
std::cout << std::boolalpha
<< "compare(\"a\", \"a\") = " << compare("a", "a") << '\n'
<< "compare(\"a\", \"b\") = " << compare("a", "b") << std::endl;
}
@@ -1,2 +0,0 @@
compare("a", "a") = true
compare("a", "b") = false
@@ -1,14 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
int main()
{
nlohmann::ordered_map<std::string, int> m;
// operator[] inserts a default-constructed value if the key doesn't exist yet
m["one"] = 1;
std::cout << "m[\"one\"] = " << m["one"] << std::endl;
// accessing again just returns the existing value
std::cout << "m[\"one\"] = " << m["one"] << std::endl;
}
@@ -1,2 +0,0 @@
m["one"] = 1
m["one"] = 1
@@ -174,20 +174,7 @@ The library maps CBOR types to JSON value types as follows:
!!! warning "Object keys" !!! warning "Object keys"
CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps with keys other than UTF-8 strings are rejected.
with keys other than text strings (major type 3) are rejected with a
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with `allow_exceptions` set
to `false`, a discarded value) naming the type of the key that was found, for instance:
```
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found an unsigned integer; last byte: 0x01
```
This applies to the [SAX interface](../parsing/sax_interface.md) as well, as the key is read before it is passed
on. This is a deliberate restriction of the library's JSON value model, not an oversight: formats built on CBOR
maps with integer keys, such as COSE ([RFC 9052](https://www.rfc-editor.org/rfc/rfc9052.html)) or CWT
([RFC 8392](https://www.rfc-editor.org/rfc/rfc8392.html)), cannot be read with this library and need a
general-purpose CBOR library instead.
!!! warning "UTF-8 validation of text strings" !!! warning "UTF-8 validation of text strings"
@@ -138,21 +138,6 @@ The library maps MessagePack types to JSON value types as follows:
Any MessagePack output created by `to_msgpack` can be successfully parsed by `from_msgpack`. Any MessagePack output created by `to_msgpack` can be successfully parsed by `from_msgpack`.
!!! warning "Object keys"
MessagePack allows map keys of any type, whereas JSON only allows strings as keys in object values. Like the
JSON-compatible [profile](https://github.com/msgpack/msgpack/blob/master/spec.md#profile) sketched in the
MessagePack specification, this library restricts map keys to `str` values. Maps with keys of any other type are
rejected with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with
`allow_exceptions` set to `false`, a discarded value) naming the type of the key that was found, for instance:
```
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found nil; last byte: 0xC0
```
This applies to the [SAX interface](../parsing/sax_interface.md) as well, as the key is read before it is passed
on. Such input needs a general-purpose MessagePack library instead.
!!! warning "UTF-8 validation of string values" !!! warning "UTF-8 validation of string values"
The MessagePack specification requires `str` values (`fixstr`, `str 8`, `str 16`, `str 32`) to be valid UTF-8. The MessagePack specification requires `str` values (`fixstr`, `str 8`, `str 16`, `str 32`) to be valid UTF-8.
+3 -3
View File
@@ -51,16 +51,16 @@ If you do want to preserve the **insertion order**, you can use the type [`nlohm
--8<-- "examples/ordered_json.output" --8<-- "examples/ordered_json.output"
``` ```
Alternatively, [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) also preserves the insertion order and, unlike [`ordered_map`](../api/ordered_map/index.md), keeps a lookup index, so it does not have the quadratic cost described below. It is used through a small adapter ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)). Alternatively, [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) also preserves the insertion order and, unlike [`ordered_map`](../api/ordered_map.md), keeps a lookup index, so it does not have the quadratic cost described below. It is used through a small adapter ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)).
If the order does not matter and you only want faster lookup, `boost::unordered_flat_map`, `absl::flat_hash_map`, `absl::node_hash_map`, and several other hash maps work through an adapter that restores the template argument order `basic_json` expects; see [Template Parameter Requirements](types/template_parameters.md#objecttype). Note these are *unordered*, not insertion-ordered. If the order does not matter and you only want faster lookup, `boost::unordered_flat_map`, `absl::flat_hash_map`, `absl::node_hash_map`, and several other hash maps work through an adapter that restores the template argument order `basic_json` expects; see [Template Parameter Requirements](types/template_parameters.md#objecttype). Note these are *unordered*, not insertion-ordered.
[`tsl::ordered_map`](https://github.com/Tessil/ordered-map) cannot be used: its iterators expose the mapped value as `const`, while `basic_json` needs to modify it in place. [`tsl::ordered_map`](https://github.com/Tessil/ordered-map) cannot be used: its iterators expose the mapped value as `const`, while `basic_json` needs to modify it in place.
The [`ordered_map`](../api/ordered_map/index.md) behind `nlohmann::ordered_json` is deliberately minimal and has no lookup The [`ordered_map`](../api/ordered_map.md) behind `nlohmann::ordered_json` is deliberately minimal and has no lookup
index, so every key access is a linear scan and building an object of `n` keys costs O(n²). This is unnoticeable at index, so every key access is a linear scan and building an object of `n` keys costs O(n²). This is unnoticeable at
typical object sizes but becomes significant for objects with many thousands of keys; see typical object sizes but becomes significant for objects with many thousands of keys; see
[`ordered_map` complexity](../api/ordered_map/index.md#complexity). The alternatives above keep a lookup index and do not [`ordered_map` complexity](../api/ordered_map.md#complexity). The alternatives above keep a lookup index and do not
have this cost. have this cost.
### Notes on parsing ### Notes on parsing
@@ -37,7 +37,7 @@ Requirements are split into two groups:
| Template parameter | Default | Notable substitutes | | Template parameter | Default | Notable substitutes |
|-------------------------------------------------------------------|-----------------------------------|-----------------------------------------------------------------------| |-------------------------------------------------------------------|-----------------------------------|-----------------------------------------------------------------------|
| [`ObjectType`](#objecttype) | `std::map` | [`nlohmann::ordered_map`](../../api/ordered_map/index.md), Abseil hash maps | | [`ObjectType`](#objecttype) | `std::map` | [`nlohmann::ordered_map`](../../api/ordered_map.md), Abseil hash maps |
| [`ArrayType`](#arraytype) | `std::vector` | `#!cpp std::deque` | | [`ArrayType`](#arraytype) | `std::vector` | `#!cpp std::deque` |
| [`StringType`](#stringtype) | `std::string` | `std::string`-like types over `char` | | [`StringType`](#stringtype) | `std::string` | `std::string`-like types over `char` |
| [`BooleanType`](#booleantype) | `bool` | none worth using | | [`BooleanType`](#booleantype) | `bool` | none worth using |
@@ -230,7 +230,7 @@ The library does not sort or de-duplicate keys itself; the behavior described in
| Container | Notes | | Container | Notes |
|----------------------------------------------------------------------------------|-------------------------------------------------------------------------------| |----------------------------------------------------------------------------------|-------------------------------------------------------------------------------|
| `#!cpp std::map` (default) | | | `#!cpp std::map` (default) | |
| [`nlohmann::ordered_map`](../../api/ordered_map/index.md) | used by [`ordered_json`](../../api/ordered_json.md); keeps insertion order | | [`nlohmann::ordered_map`](../../api/ordered_map.md) | used by [`ordered_json`](../../api/ordered_json.md); keeps insertion order |
| [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) | keeps insertion order; adapter puts `fifo_map_compare` in the comparator slot | | [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) | keeps insertion order; adapter puts `fifo_map_compare` in the comparator slot |
| `boost::container::map`, `boost::container::flat_map` | no adapter needed | | `boost::container::map`, `boost::container::flat_map` | no adapter needed |
| `#!cpp std::unordered_map` | through the adapter above; not with libstdc++ 9, see the note | | `#!cpp std::unordered_map` | through the adapter above; not with libstdc++ 9, see the note |
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -50,7 +50,7 @@ The public headers are in [`include/nlohmann`](https://github.com/nlohmann/json/
- [`adl_serializer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/adl_serializer.hpp), [`byte_container_with_subtype.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/byte_container_with_subtype.hpp), and [`ordered_map.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/ordered_map.hpp) define - [`adl_serializer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/adl_serializer.hpp), [`byte_container_with_subtype.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/byte_container_with_subtype.hpp), and [`ordered_map.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/ordered_map.hpp) define
[`adl_serializer`](../api/adl_serializer/index.md), [`adl_serializer`](../api/adl_serializer/index.md),
[`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md), and [`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md), and
[`ordered_map`](../api/ordered_map/index.md). [`ordered_map`](../api/ordered_map.md).
Everything else lives in [`detail/`](https://github.com/nlohmann/json/tree/develop/include/nlohmann/detail) and namespace `nlohmann::detail`, which is not part of the public API. Paths Everything else lives in [`detail/`](https://github.com/nlohmann/json/tree/develop/include/nlohmann/detail) and namespace `nlohmann::detail`, which is not part of the public API. Paths
below are relative to `include/nlohmann`. below are relative to `include/nlohmann`.
@@ -97,7 +97,7 @@ is generated from these files with `make amalgamate` and must not be edited by h
The library provides two specializations: The library provides two specializations:
- [`json`](../api/json.md) uses all default template arguments. - [`json`](../api/json.md) uses all default template arguments.
- [`ordered_json`](../api/ordered_json.md) uses [`ordered_map`](../api/ordered_map/index.md) as `ObjectType` to keep the - [`ordered_json`](../api/ordered_json.md) uses [`ordered_map`](../api/ordered_map.md) as `ObjectType` to keep the
insertion order of object keys. insertion order of object keys.
The requirements on the template arguments are listed in The requirements on the template arguments are listed in
+2 -9
View File
@@ -343,20 +343,13 @@ A string could not be read from a [binary format](../features/binary_formats/ind
string was read where one was required (for instance as a map key), the string's length specification is invalid, or string was read where one was required (for instance as a map key), the string's length specification is invalid, or
the string's bytes are not valid UTF-8. the string's bytes are not valid UTF-8.
CBOR and MessagePack allow map keys of any type, but JSON object keys are always strings. Maps with keys of any other
type (for instance integers or `null`) are therefore not supported; see the notes on
[CBOR](../features/binary_formats/cbor.md) and [MessagePack](../features/binary_formats/messagepack.md).
!!! failure "Example messages" !!! failure "Example messages"
``` ```
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found an unsigned integer; last byte: 0x01 [json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF
``` ```
``` ```
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack object key: only string keys are supported, but found nil; last byte: 0xC0 [json.exception.parse_error.113] parse error at byte 2: syntax error while parsing MessagePack string: expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0xFF
```
```
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x7C
``` ```
``` ```
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82 [json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82
+1 -2
View File
@@ -2,8 +2,7 @@
This page summarizes the notable changes of every release and links to the relevant documentation. This page summarizes the notable changes of every release and links to the relevant documentation.
The **complete release notes** — including all changes, the download files, and their checksums — are The **complete release notes** — including all changes, the download files, and their checksums — are
published on the [GitHub releases page](https://github.com/nlohmann/json/releases). For a raw, published on the [GitHub releases page](https://github.com/nlohmann/json/releases).
signature-level diff of the public API between releases, see [API Changes](api_changes.md).
## v3.12.0 (2025-04-11) ## v3.12.0 (2025-04-11)
+1 -30
View File
@@ -52,7 +52,6 @@ nav:
- "FAQ": home/faq.md - "FAQ": home/faq.md
- home/exceptions.md - home/exceptions.md
- home/releases.md - home/releases.md
- home/api_changes.md
- home/design_goals.md - home/design_goals.md
- home/architecture.md - home/architecture.md
- home/customers.md - home/customers.md
@@ -120,7 +119,6 @@ nav:
- 'begin': api/basic_json/begin.md - 'begin': api/basic_json/begin.md
- 'binary': api/basic_json/binary.md - 'binary': api/basic_json/binary.md
- 'binary_t': api/basic_json/binary_t.md - 'binary_t': api/basic_json/binary_t.md
- 'bjdata_version_t': api/basic_json/bjdata_version_t.md
- 'boolean_t': api/basic_json/boolean_t.md - 'boolean_t': api/basic_json/boolean_t.md
- 'cbegin': api/basic_json/cbegin.md - 'cbegin': api/basic_json/cbegin.md
- 'cbor_tag_handler_t': api/basic_json/cbor_tag_handler_t.md - 'cbor_tag_handler_t': api/basic_json/cbor_tag_handler_t.md
@@ -159,7 +157,6 @@ nav:
- 'get_to': api/basic_json/get_to.md - 'get_to': api/basic_json/get_to.md
- 'std::formatter&lt;basic_json&gt;': api/basic_json/std_formatter.md - 'std::formatter&lt;basic_json&gt;': api/basic_json/std_formatter.md
- 'std::hash&lt;basic_json&gt;': api/basic_json/std_hash.md - 'std::hash&lt;basic_json&gt;': api/basic_json/std_hash.md
- 'initializer_list_t': api/basic_json/initializer_list_t.md
- 'input_format_t': api/basic_json/input_format_t.md - 'input_format_t': api/basic_json/input_format_t.md
- 'insert': api/basic_json/insert.md - 'insert': api/basic_json/insert.md
- 'invalid_iterator': api/basic_json/invalid_iterator.md - 'invalid_iterator': api/basic_json/invalid_iterator.md
@@ -178,7 +175,6 @@ nav:
- 'is_structured': api/basic_json/is_structured.md - 'is_structured': api/basic_json/is_structured.md
- 'items': api/basic_json/items.md - 'items': api/basic_json/items.md
- 'json_base_class_t': api/basic_json/json_base_class_t.md - 'json_base_class_t': api/basic_json/json_base_class_t.md
- 'json_sax_t': api/basic_json/json_sax_t.md
- '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
@@ -236,13 +232,9 @@ nav:
- 'Overview': api/byte_container_with_subtype/index.md - 'Overview': api/byte_container_with_subtype/index.md
- '(constructor)': api/byte_container_with_subtype/byte_container_with_subtype.md - '(constructor)': api/byte_container_with_subtype/byte_container_with_subtype.md
- 'clear_subtype': api/byte_container_with_subtype/clear_subtype.md - 'clear_subtype': api/byte_container_with_subtype/clear_subtype.md
- 'container_type': api/byte_container_with_subtype/container_type.md
- 'has_subtype': api/byte_container_with_subtype/has_subtype.md - 'has_subtype': api/byte_container_with_subtype/has_subtype.md
- 'operator==': api/byte_container_with_subtype/operator_eq.md
- 'operator!=': api/byte_container_with_subtype/operator_ne.md
- 'set_subtype': api/byte_container_with_subtype/set_subtype.md - 'set_subtype': api/byte_container_with_subtype/set_subtype.md
- 'subtype': api/byte_container_with_subtype/subtype.md - 'subtype': api/byte_container_with_subtype/subtype.md
- 'subtype_type': api/byte_container_with_subtype/subtype_type.md
- adl_serializer: - adl_serializer:
- 'Overview': api/adl_serializer/index.md - 'Overview': api/adl_serializer/index.md
- 'from_json': api/adl_serializer/from_json.md - 'from_json': api/adl_serializer/from_json.md
@@ -268,46 +260,25 @@ nav:
- 'to_string': api/json_pointer/to_string.md - 'to_string': api/json_pointer/to_string.md
- json_sax: - json_sax:
- 'Overview': api/json_sax/index.md - 'Overview': api/json_sax/index.md
- '(Constructor)': api/json_sax/json_sax.md
- '(Destructor)': api/json_sax/~json_sax.md
- 'operator=': api/json_sax/operator=.md
- 'binary': api/json_sax/binary.md - 'binary': api/json_sax/binary.md
- 'binary_t': api/json_sax/binary_t.md
- 'boolean': api/json_sax/boolean.md - 'boolean': api/json_sax/boolean.md
- 'end_array': api/json_sax/end_array.md - 'end_array': api/json_sax/end_array.md
- 'end_object': api/json_sax/end_object.md - 'end_object': api/json_sax/end_object.md
- 'key': api/json_sax/key.md - 'key': api/json_sax/key.md
- 'null': api/json_sax/null.md - 'null': api/json_sax/null.md
- 'number_float': api/json_sax/number_float.md - 'number_float': api/json_sax/number_float.md
- 'number_float_t': api/json_sax/number_float_t.md
- 'number_integer': api/json_sax/number_integer.md - 'number_integer': api/json_sax/number_integer.md
- 'number_integer_t': api/json_sax/number_integer_t.md
- 'number_unsigned': api/json_sax/number_unsigned.md - 'number_unsigned': api/json_sax/number_unsigned.md
- 'number_unsigned_t': api/json_sax/number_unsigned_t.md
- 'parse_error': api/json_sax/parse_error.md - 'parse_error': api/json_sax/parse_error.md
- 'start_array': api/json_sax/start_array.md - 'start_array': api/json_sax/start_array.md
- 'start_object': api/json_sax/start_object.md - 'start_object': api/json_sax/start_object.md
- 'string': api/json_sax/string.md - 'string': api/json_sax/string.md
- 'string_t': api/json_sax/string_t.md
- 'operator<<(basic_json), operator<<(json_pointer)': api/operator_ltlt.md - 'operator<<(basic_json), operator<<(json_pointer)': api/operator_ltlt.md
- 'operator>>(basic_json)': api/operator_gtgt.md - 'operator>>(basic_json)': api/operator_gtgt.md
- 'operator""_json': api/operator_literal_json.md - 'operator""_json': api/operator_literal_json.md
- 'operator""_json_pointer': api/operator_literal_json_pointer.md - 'operator""_json_pointer': api/operator_literal_json_pointer.md
- 'ordered_json': api/ordered_json.md - 'ordered_json': api/ordered_json.md
- ordered_map: - 'ordered_map': api/ordered_map.md
- 'Overview': api/ordered_map/index.md
- '(Constructor)': api/ordered_map/ordered_map.md
- '(Destructor)': api/ordered_map/~ordered_map.md
- 'operator=': api/ordered_map/operator=.md
- 'at': api/ordered_map/at.md
- 'Container': api/ordered_map/Container.md
- 'count': api/ordered_map/count.md
- 'emplace': api/ordered_map/emplace.md
- 'erase': api/ordered_map/erase.md
- 'find': api/ordered_map/find.md
- 'insert': api/ordered_map/insert.md
- 'key_compare': api/ordered_map/key_compare.md
- 'operator[]': api/ordered_map/operator[].md
- macros: - macros:
- 'Overview': api/macros/index.md - 'Overview': api/macros/index.md
- 'JSON_ASSERT': api/macros/json_assert.md - 'JSON_ASSERT': api/macros/json_assert.md
@@ -22,9 +22,7 @@ template<typename BinaryType>
class byte_container_with_subtype : public BinaryType class byte_container_with_subtype : public BinaryType
{ {
public: public:
/// @sa https://json.nlohmann.me/api/byte_container_with_subtype/container_type/
using container_type = BinaryType; using container_type = BinaryType;
/// @sa https://json.nlohmann.me/api/byte_container_with_subtype/subtype_type/
using subtype_type = std::uint64_t; using subtype_type = std::uint64_t;
/// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/ /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/
@@ -56,14 +54,12 @@ class byte_container_with_subtype : public BinaryType
, m_has_subtype(true) , m_has_subtype(true)
{} {}
/// @sa https://json.nlohmann.me/api/byte_container_with_subtype/operator_eq/
bool operator==(const byte_container_with_subtype& rhs) const bool operator==(const byte_container_with_subtype& rhs) const
{ {
return std::tie(static_cast<const BinaryType&>(*this), m_subtype, m_has_subtype) == return std::tie(static_cast<const BinaryType&>(*this), m_subtype, m_has_subtype) ==
std::tie(static_cast<const BinaryType&>(rhs), rhs.m_subtype, rhs.m_has_subtype); std::tie(static_cast<const BinaryType&>(rhs), rhs.m_subtype, rhs.m_has_subtype);
} }
/// @sa https://json.nlohmann.me/api/byte_container_with_subtype/operator_ne/
bool operator!=(const byte_container_with_subtype& rhs) const bool operator!=(const byte_container_with_subtype& rhs) const
{ {
return !(rhs == *this); return !(rhs == *this);
+2 -168
View File
@@ -1324,80 +1324,6 @@ class binary_reader
} }
} }
/*!
@brief reads a CBOR object key
RFC 8949 allows any data item as a map key, but only strings have a
counterpart in JSON. A key of any other type is rejected with a message
naming that type, rather than the one @ref get_cbor_string gives for a
malformed string.
@param[out] result created key
@return whether key creation completed
*/
bool get_cbor_object_key(string_t& result)
{
// EOF and major type 3 (text string) are left to get_cbor_string
if (current == char_traits<char_type>::eof() || (static_cast<unsigned int>(current) & 0xE0u) == 0x60u)
{
return get_cbor_string(result);
}
const char* found = nullptr;
switch (static_cast<unsigned int>(current) >> 5u)
{
case 0:
found = "an unsigned integer";
break;
case 1:
found = "a negative integer";
break;
case 2:
found = "a byte string";
break;
case 4:
found = "an array";
break;
case 5:
found = "a map";
break;
case 6:
found = "a tag";
break;
default: // major type 7
switch (current)
{
case 0xF4:
case 0xF5:
found = "a boolean";
break;
case 0xF6:
found = "null";
break;
case 0xF7:
found = "undefined";
break;
case 0xF9:
case 0xFA:
case 0xFB:
found = "a floating-point number";
break;
case 0xFF:
found = "a break stop code";
break;
default:
found = "a simple value";
break;
}
break;
}
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(input_format_t::cbor, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
}
/*! /*!
@brief reads a definite-length CBOR byte array @brief reads a definite-length CBOR byte array
@@ -1642,7 +1568,7 @@ class binary_reader
if (top.is_object) if (top.is_object)
{ {
key.clear(); key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key) || !sax->key(key))) if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key)))
{ {
return false; return false;
} }
@@ -2143,98 +2069,6 @@ class binary_reader
} }
} }
/*!
@brief reads a MessagePack object key
The MessagePack specification allows any type as a map key, but only
strings have a counterpart in JSON. A key of any other type is rejected
with a message naming that type, rather than the one @ref
get_msgpack_string gives for a malformed string.
@param[out] result created key
@return whether key creation completed
*/
bool get_msgpack_object_key(string_t& result)
{
const char* found = nullptr;
switch (current)
{
case 0xC0:
found = "nil";
break;
case 0xC2:
case 0xC3:
found = "a boolean";
break;
case 0xCA:
case 0xCB:
found = "a float";
break;
case 0xC4:
case 0xC5:
case 0xC6:
found = "a bin";
break;
case 0xC7:
case 0xC8:
case 0xC9:
case 0xD4:
case 0xD5:
case 0xD6:
case 0xD7:
case 0xD8:
found = "an ext";
break;
case 0xCC:
case 0xCD:
case 0xCE:
case 0xCF:
case 0xD0:
case 0xD1:
case 0xD2:
case 0xD3:
found = "an integer";
break;
case 0xDC:
case 0xDD:
found = "an array";
break;
case 0xDE:
case 0xDF:
found = "a map";
break;
default:
// fixint, fixmap, and fixarray; strings, EOF, and the unused
// byte 0xC1 are left to get_msgpack_string
if (current == char_traits<char_type>::eof())
{
return get_msgpack_string(result);
}
if (current <= 0x7F || current >= 0xE0)
{
found = "an integer";
}
else if (current <= 0x8F)
{
found = "a map";
}
else if (current <= 0x9F)
{
found = "an array";
}
else
{
return get_msgpack_string(result);
}
break;
}
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
exception_message(input_format_t::msgpack, concat("only string keys are supported, but found ", found, "; last byte: 0x", last_token), "object key"), nullptr));
}
/*! /*!
@brief reads a MessagePack byte array @brief reads a MessagePack byte array
@@ -2397,7 +2231,7 @@ class binary_reader
{ {
get(); get();
key.clear(); key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_object_key(key) || !sax->key(key))) if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key)))
{ {
return false; return false;
} }
@@ -33,21 +33,15 @@ input.
template<typename BasicJsonType> template<typename BasicJsonType>
struct json_sax struct json_sax
{ {
/// @sa https://json.nlohmann.me/api/json_sax/number_integer_t/
using number_integer_t = typename BasicJsonType::number_integer_t; using number_integer_t = typename BasicJsonType::number_integer_t;
/// @sa https://json.nlohmann.me/api/json_sax/number_unsigned_t/
using number_unsigned_t = typename BasicJsonType::number_unsigned_t; using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
/// @sa https://json.nlohmann.me/api/json_sax/number_float_t/
using number_float_t = typename BasicJsonType::number_float_t; using number_float_t = typename BasicJsonType::number_float_t;
/// @sa https://json.nlohmann.me/api/json_sax/string_t/
using string_t = typename BasicJsonType::string_t; using string_t = typename BasicJsonType::string_t;
/// @sa https://json.nlohmann.me/api/json_sax/binary_t/
using binary_t = typename BasicJsonType::binary_t; using binary_t = typename BasicJsonType::binary_t;
/*! /*!
@brief a null value was read @brief a null value was read
@return whether parsing should proceed @return whether parsing should proceed
@sa https://json.nlohmann.me/api/json_sax/null/
*/ */
virtual bool null() = 0; virtual bool null() = 0;
@@ -55,7 +49,6 @@ struct json_sax
@brief a boolean value was read @brief a boolean value was read
@param[in] val boolean value @param[in] val boolean value
@return whether parsing should proceed @return whether parsing should proceed
@sa https://json.nlohmann.me/api/json_sax/boolean/
*/ */
virtual bool boolean(bool val) = 0; virtual bool boolean(bool val) = 0;
@@ -63,7 +56,6 @@ struct json_sax
@brief an integer number was read @brief an integer number was read
@param[in] val integer value @param[in] val integer value
@return whether parsing should proceed @return whether parsing should proceed
@sa https://json.nlohmann.me/api/json_sax/number_integer/
*/ */
virtual bool number_integer(number_integer_t val) = 0; virtual bool number_integer(number_integer_t val) = 0;
@@ -71,7 +63,6 @@ struct json_sax
@brief an unsigned integer number was read @brief an unsigned integer number was read
@param[in] val unsigned integer value @param[in] val unsigned integer value
@return whether parsing should proceed @return whether parsing should proceed
@sa https://json.nlohmann.me/api/json_sax/number_unsigned/
*/ */
virtual bool number_unsigned(number_unsigned_t val) = 0; virtual bool number_unsigned(number_unsigned_t val) = 0;
@@ -80,7 +71,6 @@ struct json_sax
@param[in] val floating-point value @param[in] val floating-point value
@param[in] s raw token value @param[in] s raw token value
@return whether parsing should proceed @return whether parsing should proceed
@sa https://json.nlohmann.me/api/json_sax/number_float/
*/ */
virtual bool number_float(number_float_t val, const string_t& s) = 0; virtual bool number_float(number_float_t val, const string_t& s) = 0;
@@ -89,7 +79,6 @@ struct json_sax
@param[in] val string value @param[in] val string value
@return whether parsing should proceed @return whether parsing should proceed
@note It is safe to move the passed string value. @note It is safe to move the passed string value.
@sa https://json.nlohmann.me/api/json_sax/string/
*/ */
virtual bool string(string_t& val) = 0; virtual bool string(string_t& val) = 0;
@@ -98,7 +87,6 @@ struct json_sax
@param[in] val binary value @param[in] val binary value
@return whether parsing should proceed @return whether parsing should proceed
@note It is safe to move the passed binary value. @note It is safe to move the passed binary value.
@sa https://json.nlohmann.me/api/json_sax/binary/
*/ */
virtual bool binary(binary_t& val) = 0; virtual bool binary(binary_t& val) = 0;
@@ -107,7 +95,6 @@ struct json_sax
@param[in] elements number of object elements or -1 if unknown @param[in] elements number of object elements or -1 if unknown
@return whether parsing should proceed @return whether parsing should proceed
@note binary formats may report the number of elements @note binary formats may report the number of elements
@sa https://json.nlohmann.me/api/json_sax/start_object/
*/ */
virtual bool start_object(std::size_t elements) = 0; virtual bool start_object(std::size_t elements) = 0;
@@ -116,14 +103,12 @@ struct json_sax
@param[in] val object key @param[in] val object key
@return whether parsing should proceed @return whether parsing should proceed
@note It is safe to move the passed string. @note It is safe to move the passed string.
@sa https://json.nlohmann.me/api/json_sax/key/
*/ */
virtual bool key(string_t& val) = 0; virtual bool key(string_t& val) = 0;
/*! /*!
@brief the end of an object was read @brief the end of an object was read
@return whether parsing should proceed @return whether parsing should proceed
@sa https://json.nlohmann.me/api/json_sax/end_object/
*/ */
virtual bool end_object() = 0; virtual bool end_object() = 0;
@@ -132,14 +117,12 @@ struct json_sax
@param[in] elements number of array elements or -1 if unknown @param[in] elements number of array elements or -1 if unknown
@return whether parsing should proceed @return whether parsing should proceed
@note binary formats may report the number of elements @note binary formats may report the number of elements
@sa https://json.nlohmann.me/api/json_sax/start_array/
*/ */
virtual bool start_array(std::size_t elements) = 0; virtual bool start_array(std::size_t elements) = 0;
/*! /*!
@brief the end of an array was read @brief the end of an array was read
@return whether parsing should proceed @return whether parsing should proceed
@sa https://json.nlohmann.me/api/json_sax/end_array/
*/ */
virtual bool end_array() = 0; virtual bool end_array() = 0;
@@ -149,23 +132,16 @@ struct json_sax
@param[in] last_token the last read token @param[in] last_token the last read token
@param[in] ex an exception object describing the error @param[in] ex an exception object describing the error
@return whether parsing should proceed (must return false) @return whether parsing should proceed (must return false)
@sa https://json.nlohmann.me/api/json_sax/parse_error/
*/ */
virtual bool parse_error(std::size_t position, virtual bool parse_error(std::size_t position,
const std::string& last_token, const std::string& last_token,
const detail::exception& ex) = 0; const detail::exception& ex) = 0;
/// @sa https://json.nlohmann.me/api/json_sax/json_sax/
json_sax() = default; json_sax() = default;
/// @sa https://json.nlohmann.me/api/json_sax/json_sax/
json_sax(const json_sax&) = default; json_sax(const json_sax&) = default;
/// @sa https://json.nlohmann.me/api/json_sax/json_sax/
json_sax(json_sax&&) noexcept = default; json_sax(json_sax&&) noexcept = default;
/// @sa https://json.nlohmann.me/api/json_sax/operator=/
json_sax& operator=(const json_sax&) = default; json_sax& operator=(const json_sax&) = default;
/// @sa https://json.nlohmann.me/api/json_sax/operator=/
json_sax& operator=(json_sax&&) noexcept = default; json_sax& operator=(json_sax&&) noexcept = default;
/// @sa https://json.nlohmann.me/api/json_sax/~json_sax/
virtual ~json_sax() = default; virtual ~json_sax() = default;
}; };
+39 -76
View File
@@ -206,6 +206,7 @@ class lexer : public lexer_base<BasicJsonType>
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept
: ia(std::move(adapter)) : ia(std::move(adapter))
, ignore_comments(ignore_comments_) , ignore_comments(ignore_comments_)
, decimal_point_char(static_cast<char_int_type>(get_decimal_point()))
, discard_number_values(discard_number_values_) , discard_number_values(discard_number_values_)
{} {}
@@ -221,7 +222,8 @@ class lexer : public lexer_base<BasicJsonType>
// locales // locales
///////////////////// /////////////////////
/// return the decimal point of the current locale /// return the locale-dependent decimal point
JSON_HEDLEY_PURE
static char get_decimal_point() noexcept static char get_decimal_point() noexcept
{ {
const auto* loc = localeconv(); const auto* loc = localeconv();
@@ -1090,10 +1092,9 @@ class lexer : public lexer_base<BasicJsonType>
token_type::value_float if number could be successfully scanned, token_type::value_float if number could be successfully scanned,
token_type::parse_error otherwise token_type::parse_error otherwise
@note The scanner is independent of the current locale: token_buffer @note The scanner is independent of the current locale. Internally, the
always holds `.`. Only the std::strtod fallback of convert_number() locale's decimal point is used instead of `.` to work with the
depends on the locale, and it looks up the decimal point right locale-dependent converters.
before converting (see convert_float_locale_aware()).
*/ */
token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated. token_type scan_number() // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the "done" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.
{ {
@@ -1182,7 +1183,7 @@ scan_number_zero:
{ {
case '.': case '.':
{ {
add(current); add(decimal_point_char);
decimal_point_position = token_buffer.size() - 1; decimal_point_position = token_buffer.size() - 1;
goto scan_number_decimal1; goto scan_number_decimal1;
} }
@@ -1219,7 +1220,7 @@ scan_number_any1:
case '.': case '.':
{ {
add(current); add(decimal_point_char);
decimal_point_position = token_buffer.size() - 1; decimal_point_position = token_buffer.size() - 1;
goto scan_number_decimal1; goto scan_number_decimal1;
} }
@@ -1461,9 +1462,9 @@ scan_number_done:
// Only a number below 1 can carry further insignificant zeros, and only // Only a number below 1 can carry further insignificant zeros, and only
// while the count stays at the limit does removing them change the // while the count stays at the limit does removing them change the
// answer - so this loop is skipped for all but a few tokens. The // answer - so this loop is skipped for all but a few tokens. Note
// fraction is located through decimal_point_position rather than by // token_buffer holds the locale's decimal point, so the fraction is
// searching '.'. // located through decimal_point_position rather than by searching '.'.
if (lead_zero != 0) if (lead_zero != 0)
{ {
JSON_ASSERT(has_dot != 0); // an integer "0" cannot reach the limit JSON_ASSERT(has_dot != 0); // an integer "0" cannot reach the limit
@@ -1481,8 +1482,8 @@ scan_number_done:
@brief convert the number text in token_buffer to its value and token type @brief convert the number text in token_buffer to its value and token type
The digit sequence in token_buffer has already been validated (by the The digit sequence in token_buffer has already been validated (by the
scan_number() state machine or by the contiguous fast path) and holds '.' scan_number() state machine or by the contiguous fast path) and holds the
as decimal point, independent of the locale. Integers are parsed first and fall locale decimal point in place of '.'. Integers are parsed first and fall
back to floating point on overflow. This is shared so both scanners produce back to floating point on overflow. This is shared so both scanners produce
identical results. identical results.
@@ -1562,7 +1563,7 @@ scan_number_done:
// integer conversion above overflowed. Prefer std::from_chars // integer conversion above overflowed. Prefer std::from_chars
// (Eisel-Lemire, locale-independent, correctly rounded) when available; // (Eisel-Lemire, locale-independent, correctly rounded) when available;
// otherwise the exact Clinger fast path (double only); otherwise the // otherwise the exact Clinger fast path (double only); otherwise the
// locale-aware strtof/strtod/strtold. // locale-aware strtof/strtod.
if (parse_float_from_chars(num_begin, num_end, value_float)) if (parse_float_from_chars(num_begin, num_end, value_float))
{ {
return token_type::value_float; return token_type::value_float;
@@ -1571,75 +1572,26 @@ scan_number_done:
// extra pass over the token's bytes, which otherwise shows up on // extra pass over the token's bytes, which otherwise shows up on
// high-precision inputs such as canada.json // high-precision inputs such as canada.json
if (mantissa_fits_clinger(mantissa_end) if (mantissa_fits_clinger(mantissa_end)
&& parse_float_fast(num_begin, num_end, value_float)) && parse_float_fast(num_begin, num_end, decimal_point_char, value_float))
{ {
return token_type::value_float; return token_type::value_float;
} }
convert_float_locale_aware(); char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
strtof(value_float, token_buffer.data(), &endptr);
// we checked the number format before
JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());
return token_type::value_float; return token_type::value_float;
} }
/*!
@brief convert the float in token_buffer with strtof/strtod/strtold
These functions expect the decimal point of the *current* locale, so it is
looked up right before the conversion instead of once when the lexer is
constructed: a locale change in between (by a parser callback, a SAX
handler, or another thread) must not truncate the value (#5198). The
token has been validated before, so if the conversion stops early and the
decimal point changed in the meantime, the locale changed between the
lookup and the call, and the conversion is repeated with the new decimal
point. If the decimal point did not change, a retry cannot succeed: the
locale's decimal point is not a single character (e.g., the two-byte
U+066B of ar_EG.UTF-8 or fa_IR.UTF-8) and cannot be substituted in place.
The value strtod parsed up to that point is kept, as before this change.
Note that changing the locale in another thread *while* strtod runs is
undefined behavior of the C library, which this function cannot prevent.
*/
void convert_float_locale_aware()
{
const bool has_dot = decimal_point_position != std::string::npos;
char decimal_point = get_decimal_point();
for (;;)
{
const bool substitute = has_dot && decimal_point != '.';
if (substitute)
{
token_buffer[decimal_point_position] = static_cast<typename string_t::value_type>(decimal_point);
}
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
strtof(value_float, token_buffer.data(), &endptr);
if (substitute)
{
// get_string() hands the token to the SAX interface with '.'
token_buffer[decimal_point_position] = '.';
}
if (JSON_HEDLEY_LIKELY(endptr == token_buffer.data() + token_buffer.size()))
{
return;
}
// retry only if the locale changed; otherwise, this would loop forever
const char current_decimal_point = get_decimal_point();
if (current_decimal_point == decimal_point)
{
return;
}
decimal_point = current_decimal_point;
}
}
/*! /*!
@brief contiguous fast path for scanning a number @brief contiguous fast path for scanning a number
Parses the whole number token straight from the input buffer, avoiding the Parses the whole number token straight from the input buffer, avoiding the
per-character get()/add() of scan_number(). On success it fills token_buffer per-character get()/add() of scan_number(). On success it fills token_buffer
(as scan_number() does) and (with the locale decimal point substituted, as scan_number() does) and
returns the token type. On anything it does not fully recognize as a returns the token type. On anything it does not fully recognize as a
well-formed number it makes no state change and returns well-formed number it makes no state change and returns
token_type::uninitialized, so the caller falls back to scan_number(), which token_type::uninitialized, so the caller falls back to scan_number(), which
@@ -1755,11 +1707,16 @@ scan_number_done:
} }
#endif #endif
// materialize the token exactly as scan_number() would. reset() already // materialize the token exactly as scan_number() would, substituting the
// cleared token_buffer, so append() fills it (assign() is avoided // locale decimal point so convert_number()'s strtof fallback stays valid.
// because custom string_t types need not provide it) // reset() already cleared token_buffer, so append() fills it (assign() is
// avoided because custom string_t types need not provide it)
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len); token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
decimal_point_position = dot_index; if (dot_index != std::string::npos)
{
token_buffer[dot_index] = static_cast<typename string_t::value_type>(decimal_point_char);
decimal_point_position = dot_index;
}
ia.bulk_skip(len - 1); ia.bulk_skip(len - 1);
position.chars_read_total += (len - 1); position.chars_read_total += (len - 1);
@@ -2026,7 +1983,11 @@ scan_number_done:
/// return current string value (implicitly resets the token; useful only once) /// return current string value (implicitly resets the token; useful only once)
string_t& get_string() string_t& get_string()
{ {
// a number token holds '.' regardless of the locale (#4084) // translate decimal points from locale back to '.' (#4084)
if (decimal_point_char != '.' && decimal_point_position != std::string::npos)
{
token_buffer[decimal_point_position] = '.';
}
return token_buffer; return token_buffer;
} }
@@ -2322,7 +2283,9 @@ scan_number_done:
number_unsigned_t value_unsigned = 0; number_unsigned_t value_unsigned = 0;
number_float_t value_float = 0; number_float_t value_float = 0;
/// the position of the decimal point in token_buffer /// the decimal point
const char_int_type decimal_point_char = '.';
/// the position of the decimal point in the input
std::size_t decimal_point_position = std::string::npos; std::size_t decimal_point_position = std::string::npos;
/// whether the caller (e.g. accept()/json_sax_acceptor) only needs the /// whether the caller (e.g. accept()/json_sax_acceptor) only needs the
+13 -8
View File
@@ -118,12 +118,14 @@ std::strtod. The parser only activates for number_float_t == double; float and
long double keep the std::strtof/std::strtold paths (see the templated overload long double keep the std::strtof/std::strtold paths (see the templated overload
below). below).
@param[in] first pointer to the first character of the number @param[in] first pointer to the first character of the number
@param[in] last pointer past the last character @param[in] last pointer past the last character
@param[out] out the parsed value on success @param[in] decimal_point the (locale-dependent) decimal point character
@param[out] out the parsed value on success
@return true if the value was parsed exactly; false to fall back to strtod @return true if the value was parsed exactly; false to fall back to strtod
*/ */
inline bool parse_float_fast(const char* first, const char* last, double& out) noexcept template<typename DecimalPointType>
bool parse_float_fast(const char* first, const char* last, DecimalPointType decimal_point, double& out) noexcept
{ {
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0 #if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
// Clinger's fast path is only exact when double operations are evaluated in // Clinger's fast path is only exact when double operations are evaluated in
@@ -134,6 +136,7 @@ inline bool parse_float_fast(const char* first, const char* last, double& out) n
// std::from_chars / std::strtod path. // std::from_chars / std::strtod path.
static_cast<void>(first); static_cast<void>(first);
static_cast<void>(last); static_cast<void>(last);
static_cast<void>(decimal_point);
static_cast<void>(out); static_cast<void>(out);
return false; return false;
#else #else
@@ -172,7 +175,7 @@ inline bool parse_float_fast(const char* first, const char* last, double& out) n
++num_digits; ++num_digits;
fractional_digits += static_cast<int>(seen_dot); fractional_digits += static_cast<int>(seen_dot);
} }
else if (c == '.') else if (static_cast<DecimalPointType>(c) == decimal_point)
{ {
if (JSON_HEDLEY_UNLIKELY(seen_dot)) if (JSON_HEDLEY_UNLIKELY(seen_dot))
{ {
@@ -257,8 +260,8 @@ inline bool parse_float_fast(const char* first, const char* last, double& out) n
} }
/// fast float path is only exact for `double`; decline for float/long double /// fast float path is only exact for `double`; decline for float/long double
template<typename FloatType> template<typename DecimalPointType, typename FloatType>
bool parse_float_fast(const char* /*first*/, const char* /*last*/, FloatType& /*out*/) noexcept bool parse_float_fast(const char* /*first*/, const char* /*last*/, DecimalPointType /*decimal_point*/, FloatType& /*out*/) noexcept
{ {
return false; return false;
} }
@@ -270,7 +273,9 @@ std::from_chars is locale-independent, correctly rounded, and - via the
Eisel-Lemire algorithm in modern standard libraries - much faster than strtod Eisel-Lemire algorithm in modern standard libraries - much faster than strtod
over the whole value range (not just the Clinger subset). It is used only when over the whole value range (not just the Clinger subset). It is used only when
__cpp_lib_to_chars indicates full floating-point support and only when it __cpp_lib_to_chars indicates full floating-point support and only when it
consumes the entire token ([first, last)). An under-/overflow (result_out_of_range) also declines, so consumes the entire token ([first, last)); a partial parse means the buffer
uses a non-'.' locale decimal point, in which case the caller falls back to the
locale-aware path. An under-/overflow (result_out_of_range) also declines, so
the caller's strtod fallback supplies the well-defined ±inf/0 result the parser the caller's strtod fallback supplies the well-defined ±inf/0 result the parser
expects (side-stepping the P4168 divergence between implementations). expects (side-stepping the P4168 divergence between implementations).
-1
View File
@@ -56,7 +56,6 @@ class json_pointer
public: public:
// for backwards compatibility accept BasicJsonType // for backwards compatibility accept BasicJsonType
/// @sa https://json.nlohmann.me/api/json_pointer/string_t/
using string_t = typename string_t_helper<RefStringType>::type; using string_t = typename string_t_helper<RefStringType>::type;
/// @brief create JSON pointer /// @brief create JSON pointer
+406 -208
View File
@@ -202,31 +202,22 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
using serializer = ::nlohmann::detail::serializer<basic_json>; using serializer = ::nlohmann::detail::serializer<basic_json>;
public: public:
/// @brief the type of the JSON value
/// @sa https://json.nlohmann.me/api/basic_json/value_t/
using value_t = detail::value_t; using value_t = detail::value_t;
/// JSON Pointer, see @ref nlohmann::json_pointer /// JSON Pointer, see @ref nlohmann::json_pointer
using json_pointer = ::nlohmann::json_pointer<StringType>; using json_pointer = ::nlohmann::json_pointer<StringType>;
template<typename T, typename SFINAE> template<typename T, typename SFINAE>
using json_serializer = JSONSerializer<T, SFINAE>; using json_serializer = JSONSerializer<T, SFINAE>;
/// how to treat decoding errors /// how to treat decoding errors
/// @sa https://json.nlohmann.me/api/basic_json/error_handler_t/
using error_handler_t = detail::error_handler_t; using error_handler_t = detail::error_handler_t;
/// how to treat CBOR tags /// how to treat CBOR tags
/// @sa https://json.nlohmann.me/api/basic_json/cbor_tag_handler_t/
using cbor_tag_handler_t = detail::cbor_tag_handler_t; using cbor_tag_handler_t = detail::cbor_tag_handler_t;
/// how to encode BJData /// how to encode BJData
/// @sa https://json.nlohmann.me/api/basic_json/bjdata_version_t/
using bjdata_version_t = detail::bjdata_version_t; using bjdata_version_t = detail::bjdata_version_t;
/// helper type for initializer lists of basic_json values /// helper type for initializer lists of basic_json values
/// @sa https://json.nlohmann.me/api/basic_json/initializer_list_t/
using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>; using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>;
/// @brief the type of the SAX interface used to parse and serialize the JSON value
/// @sa https://json.nlohmann.me/api/basic_json/input_format_t/
using input_format_t = detail::input_format_t; using input_format_t = detail::input_format_t;
/// SAX interface type, see @ref nlohmann::json_sax /// SAX interface type, see @ref nlohmann::json_sax
/// @sa https://json.nlohmann.me/api/basic_json/json_sax_t/
using json_sax_t = json_sax<basic_json>; using json_sax_t = json_sax<basic_json>;
//////////////// ////////////////
@@ -368,19 +359,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// the template arguments passed to class @ref basic_json. /// the template arguments passed to class @ref basic_json.
/// @{ /// @{
#if defined(JSON_HAS_CPP_14)
/// @brief default object key comparator type /// @brief default object key comparator type
/// The actual object key comparator type (@ref object_comparator_t) may be /// The actual object key comparator type (@ref object_comparator_t) may be
/// different. /// different.
/// @sa https://json.nlohmann.me/api/basic_json/default_object_comparator_t/ /// @sa https://json.nlohmann.me/api/basic_json/default_object_comparator_t/
#if defined(JSON_HAS_CPP_14)
// use of transparent comparator avoids unnecessary repeated construction of temporaries // use of transparent comparator avoids unnecessary repeated construction of temporaries
// in functions involving lookup by key with types other than object_t::key_type (aka. StringType) // in functions involving lookup by key with types other than object_t::key_type (aka. StringType)
using default_object_comparator_t = std::less<>; using default_object_comparator_t = std::less<>;
#else #else
/// @brief default object key comparator type
/// The actual object key comparator type (@ref object_comparator_t) may be
/// different.
/// @sa https://json.nlohmann.me/api/basic_json/default_object_comparator_t/
using default_object_comparator_t = std::less<StringType>; using default_object_comparator_t = std::less<StringType>;
#endif #endif
@@ -1926,7 +1913,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// other constructors and destructor // // other constructors and destructor //
/////////////////////////////////////// ///////////////////////////////////////
/// @sa https://json.nlohmann.me/api/basic_json/basic_json/
template<typename JsonRef, template<typename JsonRef,
detail::enable_if_t<detail::conjunction<detail::is_json_ref<JsonRef>, detail::enable_if_t<detail::conjunction<detail::is_json_ref<JsonRef>,
std::is_same<typename JsonRef::value_type, basic_json>>::value, int> = 0 > std::is_same<typename JsonRef::value_type, basic_json>>::value, int> = 0 >
@@ -2510,8 +2496,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@throw what @ref json_serializer<ValueType> `from_json()` method throws if conversion is required @throw what @ref json_serializer<ValueType> `from_json()` method throws if conversion is required
@since version 2.1.0 @since version 2.1.0
@sa https://json.nlohmann.me/api/basic_json/get/
*/ */
template < typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>> template < typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>>
#if defined(JSON_HAS_CPP_14) #if defined(JSON_HAS_CPP_14)
@@ -2555,8 +2539,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@sa see @ref get_ptr() for explicit pointer-member access @sa see @ref get_ptr() for explicit pointer-member access
@since version 1.0.0 @since version 1.0.0
@sa https://json.nlohmann.me/api/basic_json/get/
*/ */
template<typename PointerType, typename std::enable_if< template<typename PointerType, typename std::enable_if<
std::is_pointer<PointerType>::value, int>::type = 0> std::is_pointer<PointerType>::value, int>::type = 0>
@@ -2583,7 +2565,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// specialization to allow calling get_to with a basic_json value // specialization to allow calling get_to with a basic_json value
// see https://github.com/nlohmann/json/issues/2175 // see https://github.com/nlohmann/json/issues/2175
/// @sa https://json.nlohmann.me/api/basic_json/get_to/
template<typename ValueType, template<typename ValueType,
detail::enable_if_t < detail::enable_if_t <
detail::is_basic_json<ValueType>::value, detail::is_basic_json<ValueType>::value,
@@ -2594,7 +2575,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return v; return v;
} }
/// @sa https://json.nlohmann.me/api/basic_json/get_to/
template < template <
typename T, std::size_t N, typename T, std::size_t N,
typename Array = T (&)[N], // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) typename Array = T (&)[N], // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
@@ -2659,7 +2639,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
@since version 1.0.0 @since version 1.0.0
*/ */
/// @sa https://json.nlohmann.me/api/basic_json/operator_ValueType/
template < typename ValueType, typename std::enable_if < template < typename ValueType, typename std::enable_if <
detail::conjunction < detail::conjunction <
detail::negation<std::is_pointer<ValueType>>, detail::negation<std::is_pointer<ValueType>>,
@@ -2932,14 +2911,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// these two functions resolve a (const) char * ambiguity affecting Clang and MSVC // these two functions resolve a (const) char * ambiguity affecting Clang and MSVC
// (they seemingly cannot be constrained to resolve the ambiguity) // (they seemingly cannot be constrained to resolve the ambiguity)
/// @sa https://json.nlohmann.me/api/basic_json/operator[]/
template<typename T> template<typename T>
reference operator[](T* key) reference operator[](T* key)
{ {
return operator[](typename object_t::key_type(key)); return operator[](typename object_t::key_type(key));
} }
/// @sa https://json.nlohmann.me/api/basic_json/operator[]/
template<typename T> template<typename T>
const_reference operator[](T* key) const const_reference operator[](T* key) const
{ {
@@ -3149,7 +3126,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this)); JSON_THROW(type_error::create(306, detail::concat("cannot use value() with ", type_name()), this));
} }
/// @sa https://json.nlohmann.me/api/basic_json/value/
template < class ValueType, class BasicJsonType, detail::enable_if_t < template < class ValueType, class BasicJsonType, detail::enable_if_t <
detail::is_basic_json<BasicJsonType>::value detail::is_basic_json<BasicJsonType>::value
&& detail::is_getable<basic_json_t, ValueType>::value && detail::is_getable<basic_json_t, ValueType>::value
@@ -3160,7 +3136,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return value(ptr.convert(), default_value); return value(ptr.convert(), default_value);
} }
/// @sa https://json.nlohmann.me/api/basic_json/value/
template < class ValueType, class BasicJsonType, class ReturnType = typename value_return_type<ValueType>::type, template < class ValueType, class BasicJsonType, class ReturnType = typename value_return_type<ValueType>::type,
detail::enable_if_t < detail::enable_if_t <
detail::is_basic_json<BasicJsonType>::value detail::is_basic_json<BasicJsonType>::value
@@ -3540,7 +3515,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return ptr.contains(this); return ptr.contains(this);
} }
/// @sa https://json.nlohmann.me/api/basic_json/contains/
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0> template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens) JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
@@ -4960,7 +4934,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return result; return result;
} }
/// @sa https://json.nlohmann.me/api/basic_json/parse/
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len))
static basic_json parse(detail::span_input_adapter&& i, static basic_json parse(detail::span_input_adapter&& i,
@@ -4997,7 +4970,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true); return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
} }
/// @sa https://json.nlohmann.me/api/basic_json/accept/
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len))
static bool accept(detail::span_input_adapter&& i, static bool accept(detail::span_input_adapter&& i,
@@ -5381,7 +5353,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return result; return result;
} }
/// @sa https://json.nlohmann.me/api/basic_json/from_cbor/
template<typename T> template<typename T>
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len))
@@ -5393,7 +5364,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler); return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler);
} }
/// @sa https://json.nlohmann.me/api/basic_json/from_cbor/
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len))
static basic_json from_cbor(detail::span_input_adapter&& i, static basic_json from_cbor(detail::span_input_adapter&& i,
@@ -5449,7 +5419,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return result; return result;
} }
/// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/
template<typename T> template<typename T>
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len))
@@ -5460,7 +5429,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return from_msgpack(ptr, ptr + len, strict, allow_exceptions); return from_msgpack(ptr, ptr + len, strict, allow_exceptions);
} }
/// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len))
static basic_json from_msgpack(detail::span_input_adapter&& i, static basic_json from_msgpack(detail::span_input_adapter&& i,
@@ -5515,7 +5483,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return result; return result;
} }
/// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/
template<typename T> template<typename T>
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len))
@@ -5526,7 +5493,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return from_ubjson(ptr, ptr + len, strict, allow_exceptions); return from_ubjson(ptr, ptr + len, strict, allow_exceptions);
} }
/// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len))
static basic_json from_ubjson(detail::span_input_adapter&& i, static basic_json from_ubjson(detail::span_input_adapter&& i,
@@ -5655,7 +5621,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return result; return result;
} }
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/
template<typename T> template<typename T>
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len))
@@ -5666,7 +5631,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return from_bson(ptr, ptr + len, strict, allow_exceptions); return from_bson(ptr, ptr + len, strict, allow_exceptions);
} }
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len))
static basic_json from_bson(detail::span_input_adapter&& i, static basic_json from_bson(detail::span_input_adapter&& i,
@@ -5699,7 +5663,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return ptr.get_unchecked(this); return ptr.get_unchecked(this);
} }
/// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0> template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens) JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr) reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr)
@@ -5714,7 +5677,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return ptr.get_unchecked(this); return ptr.get_unchecked(this);
} }
/// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0> template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens) JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
const_reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr) const const_reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr) const
@@ -5729,7 +5691,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return ptr.get_checked(this); return ptr.get_checked(this);
} }
/// @sa https://json.nlohmann.me/api/basic_json/at/
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0> template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens) JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr) reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr)
@@ -5744,7 +5705,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return ptr.get_checked(this); return ptr.get_checked(this);
} }
/// @sa https://json.nlohmann.me/api/basic_json/at/
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0> template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens) JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
const_reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr) const const_reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr) const
@@ -6101,21 +6061,240 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
{ {
// the patch // the patch
basic_json result(value_t::array); basic_json result(value_t::array);
diff_recursively(result, source, target, path, 0);
return result;
}
// if the values are the same, return an empty patch private:
/// @brief two arrays or two objects @ref diff_iteratively is diffing
struct diff_frame
{
diff_frame(const basic_json* source_, const basic_json* target_, const std::size_t path_length_) noexcept
: source(source_), target(target_), path_length(path_length_)
{}
// declared for GCC's -Weffc++, which asks for them in a class with
// pointer members and a non-trivial destructor; the exception
// specifications are left implicit, as GCC 4.8 rejects explicit ones
// that differ from them
diff_frame(const diff_frame&) = default;
diff_frame(diff_frame&&) = default;
diff_frame& operator=(const diff_frame&) = default;
diff_frame& operator=(diff_frame&&) = default;
~diff_frame() = default;
/// the values being diffed, both arrays or both objects
const basic_json* source;
const basic_json* target;
/// the length of their path in `current_path`
std::size_t path_length;
/// arrays: the next index to diff
std::size_t index = 0;
/// objects: the next member of source to look at
const_iterator member{}; // NOLINT(readability-redundant-member-init)
/// objects: the keys common to both, in source's order
std::vector<typename object_t::key_type> common_keys{}; // NOLINT(readability-redundant-member-init)
/// objects: the next entry of common_keys
std::size_t next_common = 0;
/// objects: the "add" operations for keys only target has
basic_json added_ops{}; // NOLINT(readability-redundant-member-init)
};
// The operations of a diff are built by the functions below rather than
// where they are needed: building one takes several temporaries, and
// unoptimized builds give each temporary a stack slot of its own in the
// function it appears in. In diff_recursively, which is on the call stack
// once per nesting level, that made every level cost kilobytes of stack.
/// @brief append a "replace" operation for @a path with @a value to @a result
static void diff_replace(basic_json& result, const string_t& path, const basic_json& value)
{
result.push_back(
{
{"op", "replace"}, {"path", path}, {"value", value}
});
}
/// @brief append a "remove" operation for @a path to @a result
static void diff_remove(basic_json& result, const string_t& path)
{
result.push_back(object(
{
{"op", "remove"}, {"path", path}
}));
}
/// @brief append an "add" operation for @a path with @a value to @a result
static void diff_add(basic_json& result, const string_t& path, const basic_json& value)
{
result.push_back(
{
{"op", "add"}, {"path", path}, {"value", value}
});
}
/// @brief append the "remove" operations for the elements of array
/// @a source from @a index on, and the "add" operations for the
/// elements of array @a target from source's size on, to @a result
static void diff_array_tails(basic_json& result, const basic_json& source, const basic_json& target,
const string_t& path, const std::size_t index)
{
// remove my remaining elements, highest index first; appending
// in that order avoids the quadratic reinsertion done before
for (std::size_t j = source.size(); j > index; --j)
{
diff_remove(result, detail::concat<string_t>(path, '/', detail::to_string<string_t>(j - 1)));
}
// add other remaining elements
for (std::size_t i = source.size(); i < target.size(); ++i)
{
diff_add(result, detail::concat<string_t>(path, "/-"), target[i]);
}
}
/*!
@brief compare the keys of objects @a source and @a target
If the keys both objects have are in the same order in both, and the keys
only @a target has come after them, stores the keys common to both in
source's order in @a common_keys, stores the "add" operations for the keys
only @a target has in @a added_ops, and returns true: the caller then diffs
the objects member by member. Otherwise, appends operations that remove
every member of @a source and add every member of @a target to @a result,
and returns false.
*/
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
const string_t& path, std::vector<typename object_t::key_type>& common_keys,
basic_json& added_ops)
{
// first pass: record, for every source key, whether it is
// common to both objects (in source's iteration order) or
// was deleted (i.e., in source but not in target) -- this is
// a by-product of the target.find() call already needed to
// tell the two cases apart, so it adds no extra lookups. The
// "remove" ops themselves are emitted later, interleaved
// with the per-key diffs in the caller's fast path, to match
// source's original iteration order (as the original,
// pre-reordering-aware implementation did) instead of
// grouping all removes before all per-key diffs.
std::vector<typename object_t::key_type> common_keys_source_order;
for (auto it = source.cbegin(); it != source.cend(); ++it)
{
if (target.find(it.key()) != target.end())
{
common_keys_source_order.push_back(it.key());
}
}
// second pass: find keys that were added (i.e., in target but
// not in source), and record the keys common to both, in
// target's iteration order -- again a by-product of the
// source.find() call already needed to detect added keys. At
// the same time, determine whether every added key comes
// after every common key in target's order (a precondition
// for the fast path, which only ever appends new keys
// at the very end): for an object_t whose iteration order is
// a pure function of the key set (e.g. the default std::map,
// which always iterates in sorted key order), the order
// check further below is always true and this whole
// mechanism is effectively a no-op; it only matters for a
// reorderable object_t such as the one backing `ordered_json`.
// The patch ops for keys that were added (i.e., in target but not
// in source) are built here so the fast path can reuse
// them without a second source.find() per target key. Only
// used by the fast path -- the slow (reordering) path
// rebuilds "add" ops for every key itself.
std::vector<typename object_t::key_type> common_keys_target_order;
bool new_keys_form_suffix = true;
bool seen_new_key = false;
for (auto it = target.cbegin(); it != target.cend(); ++it)
{
if (source.find(it.key()) == source.end())
{
seen_new_key = true;
diff_add(added_ops, detail::concat<string_t>(path, '/', detail::escape(it.key())), it.value());
}
else
{
common_keys_target_order.push_back(it.key());
if (seen_new_key)
{
new_keys_form_suffix = false;
}
}
}
if (common_keys_source_order == common_keys_target_order && new_keys_form_suffix)
{
// fast path: order of common keys already matches (or the
// object_t's iteration order does not depend on
// insertion history), so a plain per-key diff is correct
// and minimal, as before
common_keys = std::move(common_keys_source_order);
return true;
}
// slow path: the common keys are in a different relative
// order in source and target (only possible for a
// reorderable object_t like ordered_map). Building a
// minimal reordering patch is a nontrivial (LCS-like)
// problem; instead, remove every source key -- both
// deleted keys (which must be removed regardless) and
// common keys (removed so they can be re-added in
// target's order) -- and re-add every key that should
// remain, with its final target value, in target's
// order. basic_json::patch()'s "add" operation on an
// object uses operator[], which appends at the end for a
// vector-backed insertion-ordered map when the key does
// not already exist -- so removing a key and then adding
// it moves it to the end, fixing its position.
for (auto it = source.cbegin(); it != source.cend(); ++it)
{
diff_remove(result, detail::concat<string_t>(path, '/', detail::escape(it.key())));
}
// add every key that is either common (just removed
// above) or brand new, in target's iteration order, so
// that the final order after applying the patch matches
// target exactly
for (auto it = target.cbegin(); it != target.cend(); ++it)
{
diff_add(result, detail::concat<string_t>(path, '/', detail::escape(it.key())), it.value());
}
return false;
}
/*!
@brief @ref diff, for values at nesting level @a depth, appending the
operations to @a result
Diffing two arrays or objects calls this function again, once per nesting
level, so values nested deeply enough used to exhaust the call stack and
terminate the process. The descent is bounded here: once @ref
detail::recursion_depth_limit levels have been entered, @ref
diff_iteratively diffs what is left without the call stack.
*/
static void diff_recursively(basic_json& result, const basic_json& source, const basic_json& target,
const string_t& path, const std::size_t depth)
{
// if the values are the same, there is nothing to do
if (source == target) if (source == target)
{ {
return result; return;
}
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
{
diff_iteratively(result, source, target, path);
return;
} }
if (source.type() != target.type()) if (source.type() != target.type())
{ {
// different types: replace value // different types: replace value
result.push_back( diff_replace(result, path, target);
{ return;
{"op", "replace"}, {"path", path}, {"value", target}
});
return result;
} }
switch (source.type()) switch (source.type())
@@ -6127,185 +6306,50 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
while (i < source.size() && i < target.size()) while (i < source.size() && i < target.size())
{ {
// recursive call to compare array values at index i // recursive call to compare array values at index i
auto temp_diff = diff(source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i))); diff_recursively(result, source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)), depth + 1);
result.insert(result.end(), temp_diff.begin(), temp_diff.end());
++i; ++i;
} }
// We now reached the end of at least one array // We now reached the end of at least one array
// in a second pass, traverse the remaining elements // in a second pass, traverse the remaining elements
diff_array_tails(result, source, target, path, i);
// remove my remaining elements, highest index first; appending
// in that order avoids the quadratic reinsertion done before
for (std::size_t j = source.size(); j > i; --j)
{
result.push_back(object(
{
{"op", "remove"},
{"path", detail::concat<string_t>(path, '/', detail::to_string<string_t>(j - 1))}
}));
}
i = source.size();
// add other remaining elements
while (i < target.size())
{
result.push_back(
{
{"op", "add"},
{"path", detail::concat<string_t>(path, "/-")},
{"value", target[i]}
});
++i;
}
break; break;
} }
case value_t::object: case value_t::object:
{ {
// first pass: record, for every source key, whether it is std::vector<typename object_t::key_type> common_keys;
// common to both objects (in source's iteration order) or
// was deleted (i.e., in source but not in target) -- this is
// a by-product of the target.find() call already needed to
// tell the two cases apart, so it adds no extra lookups. The
// "remove" ops themselves are emitted later, interleaved
// with the recursive per-key diffs in the fast path below,
// to match source's original iteration order (as the
// original, pre-reordering-aware implementation did) instead
// of grouping all removes before all recursive diffs.
std::vector<typename object_t::key_type> common_keys_source_order;
for (auto it = source.cbegin(); it != source.cend(); ++it)
{
if (target.find(it.key()) != target.end())
{
common_keys_source_order.push_back(it.key());
}
}
// second pass: find keys that were added (i.e., in target but
// not in source), and record the keys common to both, in
// target's iteration order -- again a by-product of the
// source.find() call already needed to detect added keys. At
// the same time, determine whether every added key comes
// after every common key in target's order (a precondition
// for the fast path below, which only ever appends new keys
// at the very end): for an object_t whose iteration order is
// a pure function of the key set (e.g. the default std::map,
// which always iterates in sorted key order), the order
// check further below is always true and this whole
// mechanism is effectively a no-op; it only matters for a
// reorderable object_t such as the one backing `ordered_json`.
// patch ops for keys that were added (i.e., in target but not
// in source); built here so the fast path below can reuse
// them without a second source.find() per target key. Only
// used by the fast path -- the slow (reordering) path
// rebuilds "add" ops for every key itself.
std::vector<typename object_t::key_type> common_keys_target_order;
basic_json added_ops(value_t::array); basic_json added_ops(value_t::array);
bool new_keys_form_suffix = true; if (diff_object_keys(result, source, target, path, common_keys, added_ops))
bool seen_new_key = false;
for (auto it = target.cbegin(); it != target.cend(); ++it)
{ {
if (source.find(it.key()) == source.end()) // fast path: common_keys is, by construction, the
{ // subsequence of source's keys that are common to both
seen_new_key = true; // objects, in source's iteration order -- so it can be
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key())); // walked in lockstep with `source` using a cheap key
added_ops.push_back( // comparison instead of another lookup. Deleted keys
{ // (those source keys not in common_keys) are interleaved
{"op", "add"}, {"path", path_key}, // here too, in source's original order, to match the
{"value", it.value()} // historical (pre-reordering-aware) output order.
}); auto common_it = common_keys.cbegin();
}
else
{
common_keys_target_order.push_back(it.key());
if (seen_new_key)
{
new_keys_form_suffix = false;
}
}
}
if (common_keys_source_order == common_keys_target_order && new_keys_form_suffix)
{
// fast path: order of common keys already matches (or the
// object_t's iteration order does not depend on
// insertion history), so a plain per-key recursive diff
// is correct and minimal, as before. common_keys_source_order
// is, by construction, the subsequence of source's keys
// that are common to both objects, in source's iteration
// order -- so it can be walked in lockstep with `source`
// using a cheap key comparison instead of another lookup.
// Deleted keys (those source keys not in common_keys_source_order)
// are interleaved here too, in source's original order, to
// match the historical (pre-reordering-aware) output order.
auto common_it = common_keys_source_order.cbegin();
for (auto it = source.cbegin(); it != source.cend(); ++it) for (auto it = source.cbegin(); it != source.cend(); ++it)
{ {
if (common_it != common_keys_source_order.cend() && it.key() == *common_it) if (common_it != common_keys.cend() && it.key() == *common_it)
{ {
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key())); diff_recursively(result, it.value(), target[it.key()], detail::concat<string_t>(path, '/', detail::escape(it.key())), depth + 1);
auto temp_diff = diff(it.value(), target[it.key()], path_key);
result.insert(result.end(), temp_diff.begin(), temp_diff.end());
++common_it; ++common_it;
} }
else else
{ {
// found a key that is not in target -> remove it // found a key that is not in target -> remove it
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key())); diff_remove(result, detail::concat<string_t>(path, '/', detail::escape(it.key())));
result.push_back(object(
{
{"op", "remove"}, {"path", path_key}
}));
} }
} }
// append the "add" ops for brand-new keys collected above // append the "add" ops for brand-new keys collected by
// during the pass over target -- no second source.find() // diff_object_keys -- no second source.find() per target
// per target key needed // key needed
result.insert(result.end(), added_ops.begin(), added_ops.end()); result.insert(result.end(), added_ops.begin(), added_ops.end());
} }
else
{
// slow path: the common keys are in a different relative
// order in source and target (only possible for a
// reorderable object_t like ordered_map). Building a
// minimal reordering patch is a nontrivial (LCS-like)
// problem; instead, remove every source key -- both
// deleted keys (which must be removed regardless) and
// common keys (removed so they can be re-added in
// target's order) -- and re-add every key that should
// remain, with its final target value, in target's
// order. basic_json::patch()'s "add" operation on an
// object uses operator[], which appends at the end for a
// vector-backed insertion-ordered map when the key does
// not already exist -- so removing a key and then adding
// it moves it to the end, fixing its position.
for (auto it = source.cbegin(); it != source.cend(); ++it)
{
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
result.push_back(object(
{
{"op", "remove"}, {"path", path_key}
}));
}
// add every key that is either common (just removed
// above) or brand new, in target's iteration order, so
// that the final order after applying the patch matches
// target exactly
for (auto it = target.cbegin(); it != target.cend(); ++it)
{
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
result.push_back(
{
{"op", "add"}, {"path", path_key},
{"value", it.value()}
});
}
}
break; break;
} }
@@ -6320,16 +6364,170 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
default: default:
{ {
// both primitive types: replace value // both primitive types: replace value
result.push_back( diff_replace(result, path, target);
{
{"op", "replace"}, {"path", path}, {"value", target}
});
break; break;
} }
} }
return result;
} }
/*!
@brief @ref diff without the call stack, appending the operations to
@a result
Produces the same operations as @ref diff_recursively. Only reached for
values nested more deeply than @ref detail::recursion_depth_limit.
*/
static void diff_iteratively(basic_json& result, const basic_json& source, const basic_json& target,
const string_t& path)
{
// The arrays and objects being diffed are kept on an explicit stack,
// and every pair of elements is still diffed completely before the
// next one, so the operations come out in the same order as in
// diff_recursively. The path of the values being diffed is kept in
// one buffer that grows and shrinks with the stack, rather than in a
// new string per level.
std::vector<diff_frame> stack;
string_t current_path = path;
// diff `s` against `t`, whose path is current_path: primitives,
// values of different types, and objects whose members were reordered
// are handled right away; arrays and other objects get a frame
const auto enter = [&result, &stack, &current_path](const basic_json & s, const basic_json & t)
{
// if the values are the same, there is nothing to do. Arrays and
// objects are not compared up front: comparing them visits
// everything below them, so doing that at every level would take
// quadratic time in the nesting depth - equal ones yield no
// operations anyway.
if ((!s.is_structured() || !t.is_structured()) && s == t)
{
return;
}
if (s.type() != t.type())
{
// different types: replace value
diff_replace(result, current_path, t);
return;
}
switch (s.type())
{
case value_t::array:
{
stack.emplace_back(&s, &t, current_path.size());
return;
}
case value_t::object:
{
std::vector<typename object_t::key_type> common_keys;
basic_json added_ops(value_t::array);
if (diff_object_keys(result, s, t, current_path, common_keys, added_ops))
{
// fast path: the frame walks source in lockstep with
// common_keys, as diff_recursively does, and appends
// added_ops once all members are done
stack.emplace_back(&s, &t, current_path.size());
stack.back().member = s.cbegin();
stack.back().common_keys = std::move(common_keys);
stack.back().added_ops = std::move(added_ops);
}
return;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
// both primitive types: replace value
diff_replace(result, current_path, t);
return;
}
}
};
enter(source, target);
while (!stack.empty())
{
// the frame is copied out member by member and changed through
// stack.back(): enter() may push a frame and the end of the loop
// pops it, either of which would invalidate a reference to it
const basic_json* const s = stack.back().source;
const basic_json* const t = stack.back().target;
const std::size_t path_length = stack.back().path_length;
const std::size_t depth = stack.size();
if (s->is_array())
{
const auto& source_array = *s->m_data.m_value.array;
const auto& target_array = *t->m_data.m_value.array;
// first pass: traverse common elements
const std::size_t i = stack.back().index;
if (i < source_array.size() && i < target_array.size())
{
++stack.back().index;
detail::concat_into(current_path, '/', detail::to_string<string_t>(i));
enter(source_array[i], target_array[i]);
if (stack.size() == depth)
{
current_path.resize(path_length);
}
continue;
}
// We now reached the end of at least one array
// in a second pass, traverse the remaining elements
diff_array_tails(result, *s, *t, current_path, i);
}
else
{
const const_iterator it = stack.back().member;
if (it != s->cend())
{
++stack.back().member;
const std::size_t next_common = stack.back().next_common;
if (next_common < stack.back().common_keys.size() && it.key() == stack.back().common_keys[next_common])
{
++stack.back().next_common;
const basic_json& target_value = (*t)[it.key()];
detail::concat_into(current_path, '/', detail::escape(it.key()));
enter(it.value(), target_value);
if (stack.size() == depth)
{
current_path.resize(path_length);
}
}
else
{
// found a key that is not in target -> remove it
diff_remove(result, detail::concat<string_t>(current_path, '/', detail::escape(it.key())));
}
continue;
}
// append the "add" ops for brand-new keys collected when the
// object was entered
result.insert(result.end(), stack.back().added_ops.begin(), stack.back().added_ops.end());
}
// this array or object is done: continue with the one it is in
stack.pop_back();
if (!stack.empty())
{
current_path.resize(stack.back().path_length);
}
}
}
public:
/// @} /// @}
//////////////////////////////// ////////////////////////////////
-35
View File
@@ -30,41 +30,30 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
{ {
using key_type = Key; using key_type = Key;
using mapped_type = T; using mapped_type = T;
/// @sa https://json.nlohmann.me/api/ordered_map/Container/
using Container = std::vector<std::pair<const Key, T>, Allocator>; using Container = std::vector<std::pair<const Key, T>, Allocator>;
using iterator = typename Container::iterator; using iterator = typename Container::iterator;
using const_iterator = typename Container::const_iterator; using const_iterator = typename Container::const_iterator;
using size_type = typename Container::size_type; using size_type = typename Container::size_type;
using value_type = typename Container::value_type; using value_type = typename Container::value_type;
#ifdef JSON_HAS_CPP_14 #ifdef JSON_HAS_CPP_14
/// @sa https://json.nlohmann.me/api/ordered_map/key_compare/
using key_compare = std::equal_to<>; using key_compare = std::equal_to<>;
#else #else
/// @sa https://json.nlohmann.me/api/ordered_map/key_compare/
using key_compare = std::equal_to<Key>; using key_compare = std::equal_to<Key>;
#endif #endif
// Explicit constructors instead of `using Container::Container` // Explicit constructors instead of `using Container::Container`
// otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4) // otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4)
/// @sa https://json.nlohmann.me/api/ordered_map/ordered_map/
ordered_map() noexcept(noexcept(Container())) : Container{} {} ordered_map() noexcept(noexcept(Container())) : Container{} {}
/// @sa https://json.nlohmann.me/api/ordered_map/ordered_map/
explicit ordered_map(const Allocator& alloc) noexcept(noexcept(Container(alloc))) : Container{alloc} {} explicit ordered_map(const Allocator& alloc) noexcept(noexcept(Container(alloc))) : Container{alloc} {}
/// @sa https://json.nlohmann.me/api/ordered_map/ordered_map/
template <class It> template <class It>
ordered_map(It first, It last, const Allocator& alloc = Allocator()) ordered_map(It first, It last, const Allocator& alloc = Allocator())
: Container{first, last, alloc} {} : Container{first, last, alloc} {}
/// @sa https://json.nlohmann.me/api/ordered_map/ordered_map/
ordered_map(std::initializer_list<value_type> init, const Allocator& alloc = Allocator() ) ordered_map(std::initializer_list<value_type> init, const Allocator& alloc = Allocator() )
: Container{init, alloc} {} : Container{init, alloc} {}
/// @sa https://json.nlohmann.me/api/ordered_map/ordered_map/
ordered_map(const ordered_map&) = default; ordered_map(const ordered_map&) = default;
/// @sa https://json.nlohmann.me/api/ordered_map/ordered_map/
ordered_map(ordered_map&&) noexcept(std::is_nothrow_move_constructible<Container>::value) = default; ordered_map(ordered_map&&) noexcept(std::is_nothrow_move_constructible<Container>::value) = default;
/// @sa https://json.nlohmann.me/api/ordered_map/~ordered_map/
~ordered_map() = default; ~ordered_map() = default;
/// @sa https://json.nlohmann.me/api/ordered_map/operator=/
ordered_map& operator=(const ordered_map& other) ordered_map& operator=(const ordered_map& other)
{ {
if (this != &other) if (this != &other)
@@ -75,14 +64,12 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return *this; return *this;
} }
/// @sa https://json.nlohmann.me/api/ordered_map/operator=/
ordered_map& operator=(ordered_map&& other) noexcept(std::is_nothrow_move_assignable<Container>::value) ordered_map& operator=(ordered_map&& other) noexcept(std::is_nothrow_move_assignable<Container>::value)
{ {
Container::operator=(std::move(static_cast<Container&>(other))); Container::operator=(std::move(static_cast<Container&>(other)));
return *this; return *this;
} }
/// @sa https://json.nlohmann.me/api/ordered_map/emplace/
std::pair<iterator, bool> emplace(const key_type& key, T&& t) std::pair<iterator, bool> emplace(const key_type& key, T&& t)
{ {
for (auto it = this->begin(); it != this->end(); ++it) for (auto it = this->begin(); it != this->end(); ++it)
@@ -96,7 +83,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return {std::prev(this->end()), true}; return {std::prev(this->end()), true};
} }
/// @sa https://json.nlohmann.me/api/ordered_map/emplace/
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
std::pair<iterator, bool> emplace(KeyType && key, T && t) std::pair<iterator, bool> emplace(KeyType && key, T && t)
@@ -112,13 +98,11 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return {std::prev(this->end()), true}; return {std::prev(this->end()), true};
} }
/// @sa https://json.nlohmann.me/api/ordered_map/operator[]/
T& operator[](const key_type& key) T& operator[](const key_type& key)
{ {
return emplace(key, T{}).first->second; return emplace(key, T{}).first->second;
} }
/// @sa https://json.nlohmann.me/api/ordered_map/operator[]/
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
T & operator[](KeyType && key) T & operator[](KeyType && key)
@@ -126,13 +110,11 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return emplace(std::forward<KeyType>(key), T{}).first->second; return emplace(std::forward<KeyType>(key), T{}).first->second;
} }
/// @sa https://json.nlohmann.me/api/ordered_map/operator[]/
const T& operator[](const key_type& key) const const T& operator[](const key_type& key) const
{ {
return at(key); return at(key);
} }
/// @sa https://json.nlohmann.me/api/ordered_map/operator[]/
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
const T & operator[](KeyType && key) const const T & operator[](KeyType && key) const
@@ -140,7 +122,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return at(std::forward<KeyType>(key)); return at(std::forward<KeyType>(key));
} }
/// @sa https://json.nlohmann.me/api/ordered_map/at/
T& at(const key_type& key) T& at(const key_type& key)
{ {
for (auto it = this->begin(); it != this->end(); ++it) for (auto it = this->begin(); it != this->end(); ++it)
@@ -154,7 +135,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
JSON_THROW(std::out_of_range("key not found")); JSON_THROW(std::out_of_range("key not found"));
} }
/// @sa https://json.nlohmann.me/api/ordered_map/at/
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
@@ -170,7 +150,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
JSON_THROW(std::out_of_range("key not found")); JSON_THROW(std::out_of_range("key not found"));
} }
/// @sa https://json.nlohmann.me/api/ordered_map/at/
const T& at(const key_type& key) const const T& at(const key_type& key) const
{ {
for (auto it = this->begin(); it != this->end(); ++it) for (auto it = this->begin(); it != this->end(); ++it)
@@ -184,7 +163,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
JSON_THROW(std::out_of_range("key not found")); JSON_THROW(std::out_of_range("key not found"));
} }
/// @sa https://json.nlohmann.me/api/ordered_map/at/
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward)
@@ -200,7 +178,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
JSON_THROW(std::out_of_range("key not found")); JSON_THROW(std::out_of_range("key not found"));
} }
/// @sa https://json.nlohmann.me/api/ordered_map/erase/
size_type erase(const key_type& key) size_type erase(const key_type& key)
{ {
for (auto it = this->begin(); it != this->end(); ++it) for (auto it = this->begin(); it != this->end(); ++it)
@@ -220,7 +197,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return 0; return 0;
} }
/// @sa https://json.nlohmann.me/api/ordered_map/erase/
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
@@ -242,13 +218,11 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return 0; return 0;
} }
/// @sa https://json.nlohmann.me/api/ordered_map/erase/
iterator erase(iterator pos) iterator erase(iterator pos)
{ {
return erase(pos, std::next(pos)); return erase(pos, std::next(pos));
} }
/// @sa https://json.nlohmann.me/api/ordered_map/erase/
iterator erase(iterator first, iterator last) iterator erase(iterator first, iterator last)
{ {
if (first == last) if (first == last)
@@ -302,7 +276,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return Container::begin() + offset; return Container::begin() + offset;
} }
/// @sa https://json.nlohmann.me/api/ordered_map/count/
size_type count(const key_type& key) const size_type count(const key_type& key) const
{ {
for (auto it = this->begin(); it != this->end(); ++it) for (auto it = this->begin(); it != this->end(); ++it)
@@ -315,7 +288,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return 0; return 0;
} }
/// @sa https://json.nlohmann.me/api/ordered_map/count/
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
size_type count(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) size_type count(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward)
@@ -330,7 +302,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return 0; return 0;
} }
/// @sa https://json.nlohmann.me/api/ordered_map/find/
iterator find(const key_type& key) iterator find(const key_type& key)
{ {
for (auto it = this->begin(); it != this->end(); ++it) for (auto it = this->begin(); it != this->end(); ++it)
@@ -343,7 +314,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return Container::end(); return Container::end();
} }
/// @sa https://json.nlohmann.me/api/ordered_map/find/
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
iterator find(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward) iterator find(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
@@ -358,7 +328,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return Container::end(); return Container::end();
} }
/// @sa https://json.nlohmann.me/api/ordered_map/find/
const_iterator find(const key_type& key) const const_iterator find(const key_type& key) const
{ {
for (auto it = this->begin(); it != this->end(); ++it) for (auto it = this->begin(); it != this->end(); ++it)
@@ -371,7 +340,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return Container::end(); return Container::end();
} }
/// @sa https://json.nlohmann.me/api/ordered_map/find/
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0> detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
const_iterator find(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward) const_iterator find(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward)
@@ -386,13 +354,11 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
return Container::end(); return Container::end();
} }
/// @sa https://json.nlohmann.me/api/ordered_map/insert/
std::pair<iterator, bool> insert( value_type&& value ) std::pair<iterator, bool> insert( value_type&& value )
{ {
return emplace(value.first, std::move(value.second)); return emplace(value.first, std::move(value.second));
} }
/// @sa https://json.nlohmann.me/api/ordered_map/insert/
std::pair<iterator, bool> insert( const value_type& value ) std::pair<iterator, bool> insert( const value_type& value )
{ {
for (auto it = this->begin(); it != this->end(); ++it) for (auto it = this->begin(); it != this->end(); ++it)
@@ -410,7 +376,6 @@ template <class Key, class T, class IgnoredLess = std::less<Key>,
using require_input_iter = typename std::enable_if<std::is_convertible<typename std::iterator_traits<InputIt>::iterator_category, using require_input_iter = typename std::enable_if<std::is_convertible<typename std::iterator_traits<InputIt>::iterator_category,
std::input_iterator_tag>::value>::type; std::input_iterator_tag>::value>::type;
/// @sa https://json.nlohmann.me/api/ordered_map/insert/
template<typename InputIt, typename = require_input_iter<InputIt>> template<typename InputIt, typename = require_input_iter<InputIt>>
void insert(InputIt first, InputIt last) void insert(InputIt first, InputIt last)
{ {
File diff suppressed because it is too large Load Diff
+2 -43
View File
@@ -1830,51 +1830,10 @@ TEST_CASE("CBOR")
SECTION("invalid string in map") SECTION("invalid string in map")
{ {
json _; json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a break stop code; last byte: 0xFF", json::parse_error&); CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF", json::parse_error&);
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded()); CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
} }
SECTION("non-string key (see #2766 and #3381)")
{
// only text strings map to JSON object keys; any other key is
// rejected with a message naming its type
const std::vector<std::pair<std::vector<std::uint8_t>, std::string>> cases =
{
{{0xA1, 0x01, 0x01}, "an unsigned integer; last byte: 0x01"},
{{0xA1, 0x20, 0x01}, "a negative integer; last byte: 0x20"},
{{0xA1, 0x41, 0x61, 0x01}, "a byte string; last byte: 0x41"},
{{0xA1, 0x80, 0x01}, "an array; last byte: 0x80"},
{{0xA1, 0xA0, 0x01}, "a map; last byte: 0xA0"},
{{0xA1, 0xC0, 0x61, 0x61, 0x01}, "a tag; last byte: 0xC0"},
{{0xA1, 0xF4, 0x01}, "a boolean; last byte: 0xF4"},
{{0xA1, 0xF5, 0x01}, "a boolean; last byte: 0xF5"},
{{0xA1, 0xF6, 0x01}, "null; last byte: 0xF6"},
{{0xA1, 0xF7, 0x01}, "undefined; last byte: 0xF7"},
{{0xA1, 0xF9, 0x3C, 0x00, 0x01}, "a floating-point number; last byte: 0xF9"},
{{0xA1, 0xFA, 0x3F, 0x80, 0x00, 0x00, 0x01}, "a floating-point number; last byte: 0xFA"},
{{0xA1, 0xFB, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, "a floating-point number; last byte: 0xFB"},
{{0xA1, 0xE0, 0x01}, "a simple value; last byte: 0xE0"},
{{0xA1, 0xF8, 0x20, 0x01}, "a simple value; last byte: 0xF8"},
// indefinite-length map
{{0xBF, 0x01, 0x01, 0xFF}, "an unsigned integer; last byte: 0x01"},
};
for (const auto& c : cases)
{
CAPTURE(c.first)
const std::string expected = "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found " + c.second;
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(c.first), expected.c_str(), json::parse_error&);
CHECK(json::from_cbor(c.first, true, false).is_discarded());
}
// a key of major type 3 with a reserved length is still reported as
// a malformed string, and a missing key as the end of input
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0x7C, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x7C", json::parse_error&);
}
SECTION("invalid UTF-8 in string (see #5529)") SECTION("invalid UTF-8 in string (see #5529)")
{ {
// a two-character text string (major type 3) whose bytes are not // a two-character text string (major type 3) whose bytes are not
@@ -2325,7 +2284,7 @@ TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
SECTION("a break marker outside an indefinite-length string is not a string") SECTION("a break marker outside an indefinite-length string is not a string")
{ {
// 0xFF only closes a string that was opened; on its own it is not one // 0xFF only closes a string that was opened; on its own it is not one
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0xFF, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR object key: only string keys are supported, but found a break stop code; last byte: 0xFF", json::parse_error&); CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0xFF, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF", json::parse_error&);
} }
} }
+1 -1
View File
@@ -666,7 +666,7 @@ TEST_CASE("parse_float_fast declines what it cannot convert exactly")
// always safe: the caller then falls back to a slower, exact conversion. // always safe: the caller then falls back to a slower, exact conversion.
const auto fast = [](const std::string & s, double & out) const auto fast = [](const std::string & s, double & out)
{ {
return nlohmann::detail::parse_float_fast(s.data(), s.data() + s.size(), out); return nlohmann::detail::parse_float_fast(s.data(), s.data() + s.size(), '.', out);
}; };
double out = 0; double out = 0;
+153
View File
@@ -15,8 +15,65 @@ using nlohmann::json;
#endif #endif
#include <fstream> #include <fstream>
#include <string>
#include <vector>
#include "make_test_data_available.hpp" #include "make_test_data_available.hpp"
namespace
{
// alternating objects and arrays nested `depth` levels deep, with members that
// depend on `variant` at some levels, so diffing two variants yields
// operations on many levels: replacing the innermost value, adding, removing,
// and (for ordered_json) reordering members, and changing array lengths
template<typename BasicJsonType>
BasicJsonType nested(const std::size_t depth, const int variant)
{
BasicJsonType value = variant;
for (std::size_t i = 0; i < depth; ++i)
{
if (i % 2 == 0)
{
BasicJsonType object = BasicJsonType::object();
if ((i + static_cast<std::size_t>(variant)) % 7 == 0)
{
object["x"] = i;
}
if (variant == 2 && i % 11 == 0)
{
object["z"] = "z";
}
object["a"] = std::move(value);
if (variant == 1 && i % 5 == 0)
{
object["y"] = 1;
}
value = std::move(object);
}
else
{
BasicJsonType array = BasicJsonType::array({std::move(value)});
if ((i + static_cast<std::size_t>(variant)) % 3 == 0)
{
array.push_back(i);
}
value = std::move(array);
}
}
return value;
}
// a path of `depth` reference tokens, as nested() nests its values
std::string nested_path(const std::size_t depth)
{
std::string path;
for (std::size_t i = depth; i > 0; --i)
{
path += (i - 1) % 2 == 0 ? "/a" : "/0";
}
return path;
}
} // namespace
TEST_CASE("JSON patch") TEST_CASE("JSON patch")
{ {
SECTION("examples from RFC 6902") SECTION("examples from RFC 6902")
@@ -1752,6 +1809,102 @@ TEST_CASE("JSON patch - diff emits array removals in descending index order")
} }
} }
TEST_CASE("JSON patch: diff of deeply nested values")
{
SECTION("the diff reproduces the target at every depth")
{
// depths on either side of the nesting depth up to which diff()
// recurses (detail::recursion_depth_limit(), 128); not every depth up
// to 300, as the test would then time out under Valgrind
std::vector<std::size_t> depths;
for (std::size_t depth = 0; depth <= 16; ++depth)
{
depths.push_back(depth);
}
for (std::size_t depth = 120; depth <= 136; ++depth)
{
depths.push_back(depth);
}
depths.push_back(300);
for (const auto depth : depths)
{
CAPTURE(depth);
for (int from = 0; from < 3; ++from)
{
for (int to = 0; to < 3; ++to)
{
CAPTURE(from);
CAPTURE(to);
const auto source = nested<json>(depth, from);
const auto target = nested<json>(depth, to);
const auto patch = json::diff(source, target);
CHECK(source.patch(patch) == target);
CHECK(patch.empty() == (from == to));
const auto ordered_source = nested<nlohmann::ordered_json>(depth, from);
const auto ordered_target = nested<nlohmann::ordered_json>(depth, to);
CHECK(ordered_source.patch(nlohmann::ordered_json::diff(ordered_source, ordered_target)) == ordered_target);
}
}
}
}
SECTION("a difference only in the innermost value is one replace operation")
{
for (std::size_t depth = 0; depth <= 300; ++depth)
{
CAPTURE(depth);
json source = 1;
json target = 2;
for (std::size_t i = 0; i < depth; ++i)
{
source = i % 2 == 0 ? json::object({{"a", std::move(source)}}) : json::array({std::move(source)});
target = i % 2 == 0 ? json::object({{"a", std::move(target)}}) : json::array({std::move(target)});
}
CHECK(json::diff(source, target, "/root") == json::array({{{"op", "replace"}, {"path", "/root" + nested_path(depth)}, {"value", 2}}}));
}
}
SECTION("values nested too deeply for the call stack (#5393)")
{
// diff() used to recurse once per nesting level, and compared the
// values with operator== on every level. The values are only
// parsed and diffed, never copied or compared, since those recurse
// too.
const std::size_t depth = 100000;
for (const bool objects :
{
false, true
})
{
CAPTURE(objects);
std::string source_text;
std::string target_text;
std::string equal_text;
std::string path;
for (std::size_t i = 0; i < depth; ++i)
{
source_text += objects ? "{\"a\":" : "[";
path += objects ? "/a" : "/0";
}
target_text = source_text + "2";
equal_text = source_text + "1";
source_text += "1";
const std::string closing(depth, objects ? '}' : ']');
const auto source = json::parse(source_text + closing);
const auto patch = json::diff(source, json::parse(target_text + closing));
REQUIRE(patch.size() == 1);
CHECK(patch[0]["op"] == "replace");
CHECK(patch[0]["path"] == path);
CHECK(patch[0]["value"] == 2);
CHECK(json::diff(source, json::parse(equal_text + closing)).empty());
}
}
}
TEST_CASE("JSON patch - every operation on ordered_json") TEST_CASE("JSON patch - every operation on ordered_json")
{ {
using nlohmann::ordered_json; using nlohmann::ordered_json;

Some files were not shown because too many files have changed in this diff Show More