Compare commits

..
Author SHA1 Message Date
Niels Lohmann 81bc423ead Add benchmarks for the binary readers
The benchmark suite covered parsing JSON text, dumping, and serializing to
CBOR, but only one binary read: FromMsgpack. Nothing measured from_cbor,
from_ubjson, from_bjdata or from_bson, so a change to binary_reader.hpp had
no baseline to be compared against.

Add read benchmarks for every format, in the two shapes that matter: from a
contiguous buffer, which is what most callers pass, and from a FILE*, which
is what FromMsgpack already measures and which compiles to different code.
FromMsgpack itself is left untouched so its numbers stay comparable across
releases. The input is derived at setup time by serializing a parsed test
file, because the test data repository ships JSON only.

The test files are wide and shallow, but the readers' cost is per container,
so add three value shapes they do not cover -- deeply nested containers, many
sibling containers, and one flat array of numbers -- plus an indefinite-length
CBOR string, a form the writer never emits and which therefore has to be
assembled by hand. UBJSON and BJData are also captured in their size- and
type-annotated form, which the readers handle in a separate code path.

BSON requires an object at the top level, so it cannot reuse the array-rooted
test files; it is captured on the object-rooted ones, and the shapes are
wrapped in an object so every format measures the same value. The setup marks
the benchmark as skipped rather than letting the exception escape if that
requirement is ever violated.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-06 16:37:22 +02:00
53 changed files with 478 additions and 1793 deletions
+2 -2
View File
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
@@ -34,7 +34,7 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
@@ -19,7 +19,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
security-events: write security-events: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
+5 -5
View File
@@ -35,7 +35,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
@@ -60,7 +60,7 @@ jobs:
target: [ci_test_amalgamation, ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind] target: [ci_test_amalgamation, ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind]
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
@@ -118,7 +118,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
@@ -369,7 +369,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
@@ -392,7 +392,7 @@ jobs:
target: [ci_test_examples, ci_test_build_documentation] target: [ci_test_examples, ci_test_build_documentation]
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with: with:
egress-policy: audit egress-policy: audit
+3 -18
View File
@@ -1,4 +1,4 @@
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef .PHONY: pretty clean ChangeLog.md release
########################################################################## ##########################################################################
# configuration # configuration
@@ -41,8 +41,6 @@ all:
@echo "fuzz_testing_ubjson - prepare fuzz testing of the UBJSON parser" @echo "fuzz_testing_ubjson - prepare fuzz testing of the UBJSON parser"
@echo "pretty - beautify code with Artistic Style" @echo "pretty - beautify code with Artistic Style"
@echo "run_benchmarks - build and run benchmarks" @echo "run_benchmarks - build and run benchmarks"
@echo "update_hedley - download Hedley and regenerate hedley.hpp / hedley_undef.hpp"
@echo "update_hedley_undef - rebuild hedley_undef.hpp from the JSON_HEDLEY_* #define names in hedley.hpp"
########################################################################## ##########################################################################
@@ -243,24 +241,11 @@ update_hedley:
rm -f include/nlohmann/thirdparty/hedley/hedley.hpp include/nlohmann/thirdparty/hedley/hedley_undef.hpp rm -f include/nlohmann/thirdparty/hedley/hedley.hpp include/nlohmann/thirdparty/hedley/hedley_undef.hpp
curl https://raw.githubusercontent.com/nemequ/hedley/master/hedley.h -o include/nlohmann/thirdparty/hedley/hedley.hpp curl https://raw.githubusercontent.com/nemequ/hedley/master/hedley.h -o include/nlohmann/thirdparty/hedley/hedley.hpp
$(SED) -i 's/HEDLEY_/JSON_HEDLEY_/g' include/nlohmann/thirdparty/hedley/hedley.hpp $(SED) -i 's/HEDLEY_/JSON_HEDLEY_/g' include/nlohmann/thirdparty/hedley/hedley.hpp
grep "[[:blank:]]*#[[:blank:]]*undef" include/nlohmann/thirdparty/hedley/hedley.hpp | grep -v "__" | sort | uniq | $(SED) 's/ //g' | $(SED) 's/undef/undef /g' > include/nlohmann/thirdparty/hedley/hedley_undef.hpp
$(SED) -i '1s/^/#pragma once\n\n/' include/nlohmann/thirdparty/hedley/hedley.hpp $(SED) -i '1s/^/#pragma once\n\n/' include/nlohmann/thirdparty/hedley/hedley.hpp
$(MAKE) update_hedley_undef $(SED) -i '1s/^/#pragma once\n\n/' include/nlohmann/thirdparty/hedley/hedley_undef.hpp
$(MAKE) amalgamate $(MAKE) amalgamate
# Rebuild hedley_undef.hpp from every JSON_HEDLEY_* name that hedley.hpp
# #defines. Hedley does not #undef all of its public macros internally (see
# #5408), so grepping those #undef lines misses names such as
# JSON_HEDLEY_PRAGMA. cmake/scripts/gen_hedley_undef_check.cmake is the
# single source of truth for this extraction (tests/CMakeLists.txt uses the
# same script, in MODE=checks, to generate the matching leak-check test), so
# the vendored header, the generated #undef list, and the regression test
# cannot drift apart.
update_hedley_undef:
cmake -DHEDLEY_HPP=include/nlohmann/thirdparty/hedley/hedley.hpp \
-DOUTPUT=include/nlohmann/thirdparty/hedley/hedley_undef.hpp \
-DMODE=undef \
-P cmake/scripts/gen_hedley_undef_check.cmake
########################################################################## ##########################################################################
# serve_header.py # serve_header.py
########################################################################## ##########################################################################
-112
View File
@@ -1,112 +0,0 @@
# Shared extractor for the JSON_HEDLEY_* macro names defined in hedley.hpp.
#
# Every macro that hedley.hpp #defines must be #undef-ed again once json.hpp
# has been fully processed (see include/nlohmann/detail/macro_unscope.hpp
# and https://github.com/nlohmann/json/issues/5408). Deriving the macro list
# straight from hedley.hpp here -- instead of hand-maintaining it in two
# places -- means hedley_undef.hpp and the regression test that checks for
# leaked macros can never drift apart, even after a future `make
# update_hedley` pulls in new macros from upstream Hedley.
#
# MODE=undef (default): write hedley_undef.hpp (SPDX header, #pragma once,
# one #undef per macro name) -- used by `make update_hedley_undef`
# MODE=checks: write one #ifdef/FAIL_CHECK/#endif per macro name,
# meant to be #include-d inside a TEST_CASE -- used by
# tests/CMakeLists.txt to (re)generate the include for
# tests/src/unit-no-macro-leak.cpp
#
# Required variables:
# HEDLEY_HPP path to include/nlohmann/thirdparty/hedley/hedley.hpp
# OUTPUT path of the file to (over)write
# Optional:
# MODE "undef" (default) or "checks"
if(NOT DEFINED HEDLEY_HPP OR NOT DEFINED OUTPUT)
message(FATAL_ERROR "HEDLEY_HPP and OUTPUT must be set")
endif()
if(NOT EXISTS "${HEDLEY_HPP}")
message(FATAL_ERROR "Hedley header not found: ${HEDLEY_HPP}")
endif()
if(NOT DEFINED MODE)
set(MODE undef)
endif()
if(NOT MODE STREQUAL "undef" AND NOT MODE STREQUAL "checks")
message(FATAL_ERROR "MODE must be undef or checks, got: ${MODE}")
endif()
# Line-anchored, like `grep -oE "^[[:blank:]]*#[[:blank:]]*define[[:blank:]]+JSON_HEDLEY_[A-Za-z0-9_]+"`.
# Unanchored matching would also pick up JSON_HEDLEY_* mentions inside
# comments or string literals elsewhere in the file, which must not turn
# into #undef lines.
file(STRINGS "${HEDLEY_HPP}" hedley_lines)
set(macro_names)
foreach(line IN LISTS hedley_lines)
if("${line}" MATCHES "^[ \t]*#[ \t]*define[ \t]+(JSON_HEDLEY_[A-Za-z0-9_]+)")
list(APPEND macro_names "${CMAKE_MATCH_1}")
endif()
endforeach()
if(NOT macro_names)
message(FATAL_ERROR "No JSON_HEDLEY_* macros found in ${HEDLEY_HPP}")
endif()
list(REMOVE_DUPLICATES macro_names)
# Lexicographic, locale-independent (ASCII-only names) -- matches `LC_ALL=C sort`.
list(SORT macro_names COMPARE STRING)
list(LENGTH macro_names macro_count)
set(generated "")
if(MODE STREQUAL "undef")
# Same banner `make update_hedley_undef` would stamp by hand, so the
# recipe is self-contained and its output is byte-stable across reruns.
# The embedded SPDX tags below are part of the *generated* file's
# content, not a REUSE header for this .cmake script itself (which is
# already covered by the blanket "Files: *" rule in .reuse/dep5) -- keep
# them wrapped in REUSE-IgnoreStart/End so `reuse lint` does not try to
# parse "MIT\n")" as this file's own SPDX-License-Identifier value.
# REUSE-IgnoreStart
string(APPEND generated "// __ _____ _____ _____\n")
string(APPEND generated "// __| | __| | | | JSON for Modern C++\n")
string(APPEND generated "// | | |__ | | | | | | version 3.12.0\n")
string(APPEND generated "// |_____|_____|_____|_|___| https://github.com/nlohmann/json\n")
string(APPEND generated "//\n")
string(APPEND generated "// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>\n")
string(APPEND generated "// SPDX-License-Identifier: MIT\n")
# REUSE-IgnoreEnd
string(APPEND generated "\n")
string(APPEND generated "#pragma once\n")
string(APPEND generated "\n")
foreach(name IN LISTS macro_names)
string(APPEND generated "#undef ${name}\n")
endforeach()
else()
string(APPEND generated "// This file is generated by cmake/scripts/gen_hedley_undef_check.cmake\n")
string(APPEND generated "// from include/nlohmann/thirdparty/hedley/hedley.hpp. Do not edit it by\n")
string(APPEND generated "// hand -- it is regenerated on every build. ${macro_count} macros checked.\n\n")
foreach(name IN LISTS macro_names)
string(APPEND generated "#ifdef ${name}\n")
string(APPEND generated " FAIL_CHECK(\"${name} leaked after including nlohmann/json.hpp\");\n")
string(APPEND generated "#endif\n")
endforeach()
endif()
get_filename_component(output_dir "${OUTPUT}" DIRECTORY)
if(output_dir)
file(MAKE_DIRECTORY "${output_dir}")
endif()
# Avoid rewriting the file (and busting downstream incremental rebuilds)
# when the content has not actually changed.
set(write_output TRUE)
if(EXISTS "${OUTPUT}")
file(READ "${OUTPUT}" existing_content)
if(existing_content STREQUAL generated)
set(write_output FALSE)
endif()
endif()
if(write_output)
file(WRITE "${OUTPUT}" "${generated}")
endif()
-8
View File
@@ -34,10 +34,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
("add", "remove", "move") ("add", "remove", "move")
- Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target - Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target
location has a parent that is neither an object nor an array. location has a parent that is neither an object nor an array.
- Throws [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) if a "remove" operation's target
location has a parent that is neither an object nor an array.
- Throws [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) if a "move" operation's "from"
location is a proper prefix of its "path" location.
- Throws [`other_error.501`](../../home/exceptions.md#jsonexceptionother_error501) if "test" operation was - Throws [`other_error.501`](../../home/exceptions.md#jsonexceptionother_error501) if "test" operation was
unsuccessful. unsuccessful.
@@ -79,7 +75,3 @@ is thrown. In any case, the original value is not changed: the patch is applied
- Added in version 2.0.0. - Added in version 2.0.0.
- Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's - Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's
target location has a non-object/non-array parent in version 3.13.0. target location has a non-object/non-array parent in version 3.13.0.
- Added [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) and stopped silently ignoring a "remove" operation whose target
location has a non-object/non-array parent in version 3.13.0.
- Added [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) and rejected a "move" operation whose "from" location is a proper
prefix of its "path" location instead of silently producing a corrupted result in version 3.13.0.
@@ -30,10 +30,6 @@ No guarantees, value may be corrupted by an unsuccessful patch operation.
("add", "remove", "move") ("add", "remove", "move")
- Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target - Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target
location has a parent that is neither an object nor an array. location has a parent that is neither an object nor an array.
- Throws [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) if a "remove" operation's target
location has a parent that is neither an object nor an array.
- Throws [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) if a "move" operation's "from"
location is a proper prefix of its "path" location.
- Throws [`other_error.501`](../../home/exceptions.md#jsonexceptionother_error501) if "test" operation was - Throws [`other_error.501`](../../home/exceptions.md#jsonexceptionother_error501) if "test" operation was
unsuccessful. unsuccessful.
@@ -76,7 +72,3 @@ function throws an exception.
- Added in version 3.11.0. - Added in version 3.11.0.
- Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's - Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's
target location has a non-object/non-array parent in version 3.13.0. target location has a non-object/non-array parent in version 3.13.0.
- Added [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) and stopped silently ignoring a "remove" operation whose target
location has a non-object/non-array parent in version 3.13.0.
- Added [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) and rejected a "move" operation whose "from" location is a proper
prefix of its "path" location instead of silently producing a corrupted result in version 3.13.0.
@@ -4,8 +4,8 @@
Hello, world! Hello, world!
1 2 3 4 5 1 2 3 4 5
string: "Hello, world!"
number: {"floating-point":17.23,"integer":42} number: {"floating-point":17.23,"integer":42}
null: null null: null
string: "Hello, world!"
boolean: true boolean: true
array: [1,2,3,4,5] array: [1,2,3,4,5]
+1 -1
View File
@@ -4,8 +4,8 @@
Hello, world! Hello, world!
1 2 3 4 5 1 2 3 4 5
string: "Hello, world!"
number: {"floating-point":17.23,"integer":42} number: {"floating-point":17.23,"integer":42}
null: null null: null
string: "Hello, world!"
boolean: true boolean: true
array: [1,2,3,4,5] array: [1,2,3,4,5]
@@ -4,9 +4,9 @@
Hello, world! Hello, world!
1 2 3 4 5 1 2 3 4 5
string: "Hello, world!"
number: {"floating-point":17.23,"integer":42} number: {"floating-point":17.23,"integer":42}
null: null null: null
string: "Hello, world!"
boolean: true boolean: true
array: [1,2,3,4,5] array: [1,2,3,4,5]
[json.exception.type_error.302] type must be boolean, but is string [json.exception.type_error.302] type must be boolean, but is string
-28
View File
@@ -933,34 +933,6 @@ BSON stores the length of documents, arrays, strings, and binary values in a sig
[`to_bson`](../api/basic_json/to_bson.md) produced documents with negative length prefixes that [`to_bson`](../api/basic_json/to_bson.md) produced documents with negative length prefixes that
[`from_bson`](../api/basic_json/from_bson.md) rejected. [`from_bson`](../api/basic_json/from_bson.md) rejected.
### json.exception.out_of_range.413
A JSON Patch `remove` operation cannot be applied because the target location's parent is neither an object nor an array. Per [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902), a `remove` target must reference a member of an existing object or an element of an existing array; a primitive value (string, number, boolean, etc.) or `null` has no members or elements to remove.
!!! failure "Example message"
```
cannot remove value: the JSON Patch 'remove' target's parent is of type number, but must be an object or array
```
!!! note
This exception was added in version 3.13.0. Before that, this situation was silently ignored (the `remove` operation had no effect).
### json.exception.out_of_range.414
A JSON Patch `move` operation's `"from"` location is a proper prefix of its `"path"` location. Per [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) (section 4.4), a location cannot be moved into one of its own children.
!!! failure "Example message"
```
cannot move value: 'from' path '/0' is a proper prefix of 'path' '/0/0'
```
!!! note
This exception was added in version 3.13.0. Before that, this situation could succeed with a corrupted result: for an array target, removing the "from" element before the "add" step shifted subsequent indices, so "path" silently re-resolved to a different element than intended.
## Further exceptions ## Further exceptions
This exception is thrown in case of errors that cannot be classified with the This exception is thrown in case of errors that cannot be classified with the
@@ -398,17 +398,6 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
} }
} }
template<typename ConstructibleObjectType>
auto from_json_object_reserve(ConstructibleObjectType& obj, typename ConstructibleObjectType::size_type size, priority_tag<1> /*unused*/)
-> decltype(obj.reserve(size), void())
{
obj.reserve(size);
}
template<typename ConstructibleObjectType>
inline void from_json_object_reserve(ConstructibleObjectType& /*obj*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
{}
template<typename BasicJsonType, typename ConstructibleObjectType, template<typename BasicJsonType, typename ConstructibleObjectType,
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0> enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
@@ -420,7 +409,6 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
ConstructibleObjectType ret; ConstructibleObjectType ret;
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>(); const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {});
for (const auto& p : *inner_object) for (const auto& p : *inner_object)
{ {
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>()); ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
@@ -1075,8 +1075,8 @@ char* to_chars(char* first, const char* last, FloatType value)
} }
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_PUSH #pragma GCC diagnostic push
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal") #pragma GCC diagnostic ignored "-Wfloat-equal"
#endif #endif
if (value == 0) // +-0 if (value == 0) // +-0
{ {
@@ -1087,7 +1087,7 @@ char* to_chars(char* first, const char* last, FloatType value)
return first; return first;
} }
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP #pragma GCC diagnostic pop
#endif #endif
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10); JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10);
+3 -3
View File
@@ -33,8 +33,8 @@
// code stumbling over this. See https://github.com/nlohmann/json/issues/4087 // code stumbling over this. See https://github.com/nlohmann/json/issues/4087
// for a discussion. // for a discussion.
#if defined(__clang__) #if defined(__clang__)
JSON_HEDLEY_DIAGNOSTIC_PUSH #pragma clang diagnostic push
JSON_HEDLEY_PRAGMA(clang diagnostic ignored "-Wweak-vtables") #pragma clang diagnostic ignored "-Wweak-vtables"
#endif #endif
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
@@ -287,5 +287,5 @@ class other_error : public exception
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
#if defined(__clang__) #if defined(__clang__)
JSON_HEDLEY_DIAGNOSTIC_POP #pragma clang diagnostic pop
#endif #endif
+5 -128
View File
@@ -149,11 +149,10 @@ class lexer : public lexer_base<BasicJsonType>
public: public:
using token_type = typename lexer_base<BasicJsonType>::token_type; using token_type = typename lexer_base<BasicJsonType>::token_type;
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = 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())) , decimal_point_char(static_cast<char_int_type>(get_decimal_point()))
, discard_number_values(discard_number_values_)
{} {}
// deleted because of pointer members // deleted because of pointer members
@@ -1280,58 +1279,6 @@ scan_number_done:
// we are done scanning a number) // we are done scanning a number)
unget(); unget();
// If the caller does not need the converted value (only whether the
// input is syntactically valid; see json_sax_acceptor/accept()), an
// unsigned/integer token can be reported without calling
// strtoull()/strtoll() at all, *provided* we can already tell from
// the digit count alone that the conversion cannot overflow 64 bits.
// Such tokens are always finite and are accepted unconditionally by
// the parser regardless of their actual value (parser::sax_parse_internal()
// never checks finiteness for value_unsigned/value_integer), so the
// classification below is all that is needed.
//
// A decimal number with up to 18 digits is always representable in
// both std::uint64_t and std::int64_t (18 nines is ~1e18, well below
// both UINT64_MAX ~1.8e19 and INT64_MAX ~9.2e18), so strtoull()/strtoll()
// could not have set errno to ERANGE for it. Numbers with more digits
// (rare in practice) fall through to the exact code below, unchanged,
// so their handling -- including reclassification to value_float when
// the value overflows 64 bits, and rejection when it is not even
// finite as a double -- is bit-for-bit identical to before this
// optimization.
//
// Note this reasons about std::uint64_t/std::int64_t, not about
// number_unsigned_t/number_integer_t (BasicJsonType's own, possibly
// narrower, template parameters -- e.g. std::uint32_t). That is fine
// *only* because discard_number_values is exclusively set by
// accept() (see json.hpp), and accept() always parses through the
// library's own json_sax_acceptor -- never a user-supplied SAX
// consumer -- whose number_unsigned()/number_integer()/number_float()
// callbacks unconditionally discard their argument and return true.
// So for every caller that can reach this branch, neither the token
// classification below nor the eventual (possibly narrowed, and on
// this fast path left stale/unset) value_unsigned/value_integer is
// ever consulted -- an unsigned/integer token is accepted outright,
// and even a >18-digit token that this fast path deliberately falls
// through for is, once reclassified to value_float, still finite
// (and thus accepted) for any digit count that fits in number_unsigned_t
// or number_integer_t regardless of that type's width. If this
// function is ever taught to run with discard_number_values true for
// a caller that *does* read the converted value, this reasoning (and
// the fast path below) would need to be revisited.
if (discard_number_values)
{
constexpr std::size_t safe_digit_count = 18;
if (number_type == token_type::value_unsigned && token_buffer.size() <= safe_digit_count)
{
return token_type::value_unsigned;
}
if (number_type == token_type::value_integer && token_buffer.size() - 1 <= safe_digit_count)
{
return token_type::value_integer;
}
}
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg) char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
errno = 0; errno = 0;
@@ -1446,7 +1393,8 @@ scan_number_done:
*/ */
char_int_type get() char_int_type get()
{ {
advance_position(); ++position.chars_read_total;
++position.chars_read_current_line;
if (next_unget) if (next_unget)
{ {
@@ -1458,23 +1406,6 @@ scan_number_done:
current = ia.get_character(); current = ia.get_character();
} }
return track_after_read();
}
/// shared head of get() / get_ignoring_pending_unget(): bump the
/// per-character position counters (line-count-on-'\n' bookkeeping is
/// handled afterwards, in track_after_read(), once `current` is known)
void advance_position() noexcept
{
++position.chars_read_total;
++position.chars_read_current_line;
}
/// shared tail of get() / get_ignoring_pending_unget(): capture the
/// character for error messages (if needed) and update line/column
/// bookkeeping for the character now in `current`
char_int_type track_after_read()
{
// seekable adapters reconstruct the token lazily on error (see // seekable adapters reconstruct the token lazily on error (see
// get_token_string), so the eager per-character copy is skipped // get_token_string), so the eager per-character copy is skipped
capture_char(std::integral_constant<bool, lazy_token_string> {}); capture_char(std::integral_constant<bool, lazy_token_string> {});
@@ -1488,29 +1419,6 @@ scan_number_done:
return current; return current;
} }
/*!
@brief like get(), but for call sites that can prove no unget() is pending
get() has to check the `next_unget` flag on every call, because a
previous token may have ended with unget() (e.g. scan_number() always
ungets the character that terminated the number, so the next call to
scan() can see it again). skip_whitespace() reads that first,
possibly-ungotten character via a plain get(), but every further
character it reads is guaranteed to be a fresh read: nothing between
those calls invokes unget(). This variant skips the (otherwise always
false) next_unget branch for those calls; it is not a general
replacement for get().
*/
char_int_type get_ignoring_pending_unget()
{
JSON_ASSERT(!next_unget);
advance_position();
current = ia.get_character();
return track_after_read();
}
/// seekable adapter: nothing to capture, the token is rebuilt on error /// seekable adapter: nothing to capture, the token is rebuilt on error
void capture_char(std::true_type /*lazy*/) const noexcept {} void capture_char(std::true_type /*lazy*/) const noexcept {}
@@ -1704,37 +1612,13 @@ scan_number_done:
return true; return true;
} }
/// whether `current` is one of the four JSON whitespace characters
bool current_is_whitespace() const noexcept
{
return current == ' ' || current == '\t' || current == '\n' || current == '\r';
}
void skip_whitespace() void skip_whitespace()
{ {
// the first character may be a pending unget() left over from the
// previous token (see get_ignoring_pending_unget()); every
// subsequent character read by this loop is guaranteed fresh, since
// nothing below calls unget()
get();
if (!current_is_whitespace())
{
return;
}
// this is written as an if-guarded do-while (rather than a plain
// while loop) because that shape is what lets both GCC and Clang
// keep the input adapter's read pointer in a register across
// iterations; the equivalent while-loop measurably defeated that
// optimization in testing, turning long whitespace runs (e.g. the
// indentation of pretty-printed JSON) from a register-only loop
// into one that reloads the pointer from memory every character
do do
{ {
get_ignoring_pending_unget(); get();
} }
while (current_is_whitespace()); while (current == ' ' || current == '\t' || current == '\n' || current == '\r');
} }
token_type scan() token_type scan()
@@ -1870,13 +1754,6 @@ scan_number_done:
const char_int_type decimal_point_char = '.'; const char_int_type decimal_point_char = '.';
/// the position of the decimal point in the input /// 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
/// token classification and never looks at the converted numeric value;
/// when set, scan_number() may skip strtoull()/strtoll() for
/// value_unsigned/value_integer tokens whose digit count guarantees they
/// fit into 64 bits (see scan_number())
const bool discard_number_values = false;
}; };
} // namespace detail } // namespace detail
+2 -3
View File
@@ -72,10 +72,9 @@ class parser
parser_callback_t<BasicJsonType> cb = nullptr, parser_callback_t<BasicJsonType> cb = nullptr,
const bool allow_exceptions_ = true, const bool allow_exceptions_ = true,
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas_ = false, const bool ignore_trailing_commas_ = false)
const bool discard_number_values_ = false)
: callback(std::move(cb)) : callback(std::move(cb))
, m_lexer(std::move(adapter), ignore_comments, discard_number_values_) , m_lexer(std::move(adapter), ignore_comments)
, allow_exceptions(allow_exceptions_) , allow_exceptions(allow_exceptions_)
, ignore_trailing_commas(ignore_trailing_commas_) , ignore_trailing_commas(ignore_trailing_commas_)
{ {
@@ -18,7 +18,6 @@
#endif #endif
#include <nlohmann/detail/abi_macros.hpp> #include <nlohmann/detail/abi_macros.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/type_traits.hpp> #include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/detail/string_utils.hpp> #include <nlohmann/detail/string_utils.hpp>
#include <nlohmann/detail/value_t.hpp> #include <nlohmann/detail/value_t.hpp>
@@ -207,10 +206,10 @@ NLOHMANN_JSON_NAMESPACE_END
namespace std namespace std
{ {
// Fix: https://github.com/nlohmann/json/issues/1401
#if defined(__clang__) #if defined(__clang__)
JSON_HEDLEY_DIAGNOSTIC_PUSH // Fix: https://github.com/nlohmann/json/issues/1401
JSON_HEDLEY_PRAGMA(clang diagnostic ignored "-Wmismatched-tags") #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmismatched-tags"
#endif #endif
template<typename IteratorType> template<typename IteratorType>
class tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>> // NOLINT(cert-dcl58-cpp) class tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>> // NOLINT(cert-dcl58-cpp)
@@ -225,7 +224,7 @@ class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >>
::nlohmann::detail::iteration_proxy_value<IteratorType >> ())); ::nlohmann::detail::iteration_proxy_value<IteratorType >> ()));
}; };
#if defined(__clang__) #if defined(__clang__)
JSON_HEDLEY_DIAGNOSTIC_POP #pragma clang diagnostic pop
#endif #endif
} // namespace std } // namespace std
-14
View File
@@ -748,20 +748,6 @@ class json_pointer
} }
} }
// the reference token consists only of digits at this point (cf. checks
// above); however, its numeric value might not be representable, in which
// case array_index() would throw out_of_range.404/410 -- contains() must
// not throw (see #5395), so such a reference token is treated as "not found"
errno = 0; // strtoull() does not reset errno on success
char* p_end = nullptr; // NOLINT(misc-const-correctness)
const unsigned long long magnitude = std::strtoull(reference_token.c_str(), &p_end, 10); // NOLINT(runtime/int)
if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX
|| magnitude >= static_cast<unsigned long long>((std::numeric_limits<typename BasicJsonType::size_type>::max)()))) // NOLINT(runtime/int)
{
// the array index cannot be represented as size_type
return false;
}
const auto idx = array_index<BasicJsonType>(reference_token); const auto idx = array_index<BasicJsonType>(reference_token);
if (idx >= ptr->size()) if (idx >= ptr->size())
{ {
@@ -1850,8 +1850,8 @@ class binary_writer
void write_compact_float(const number_float_t n, detail::input_format_t format) void write_compact_float(const number_float_t n, detail::input_format_t format)
{ {
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_PUSH #pragma GCC diagnostic push
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal") #pragma GCC diagnostic ignored "-Wfloat-equal"
#endif #endif
if (!std::isfinite(n) || ((static_cast<double>(n) >= static_cast<double>(std::numeric_limits<float>::lowest()) && if (!std::isfinite(n) || ((static_cast<double>(n) >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
static_cast<double>(n) <= static_cast<double>((std::numeric_limits<float>::max)()) && static_cast<double>(n) <= static_cast<double>((std::numeric_limits<float>::max)()) &&
@@ -1870,7 +1870,7 @@ class binary_writer
write_number(n); write_number(n);
} }
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP #pragma GCC diagnostic pop
#endif #endif
} }
+28 -198
View File
@@ -164,12 +164,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
detail::parser_callback_t<basic_json>cb = nullptr, detail::parser_callback_t<basic_json>cb = nullptr,
const bool allow_exceptions = true, const bool allow_exceptions = true,
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false, const bool ignore_trailing_commas = false
const bool discard_number_values = false
) )
{ {
return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter), return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter),
std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas, discard_number_values); std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
} }
private: private:
@@ -1336,7 +1335,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief serialization /// @brief serialization
/// @sa https://json.nlohmann.me/api/basic_json/dump/ /// @sa https://json.nlohmann.me/api/basic_json/dump/
JSON_HEDLEY_WARN_UNUSED_RESULT
string_t dump(const int indent = -1, string_t dump(const int indent = -1,
const char indent_char = ' ', const char indent_char = ' ',
const bool ensure_ascii = false, const bool ensure_ascii = false,
@@ -1359,7 +1357,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return the type of the JSON value (explicit) /// @brief return the type of the JSON value (explicit)
/// @sa https://json.nlohmann.me/api/basic_json/type/ /// @sa https://json.nlohmann.me/api/basic_json/type/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr value_t type() const noexcept constexpr value_t type() const noexcept
{ {
return m_data.m_type; return m_data.m_type;
@@ -1367,7 +1364,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether type is primitive /// @brief return whether type is primitive
/// @sa https://json.nlohmann.me/api/basic_json/is_primitive/ /// @sa https://json.nlohmann.me/api/basic_json/is_primitive/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_primitive() const noexcept constexpr bool is_primitive() const noexcept
{ {
return is_null() || is_string() || is_boolean() || is_number() || is_binary(); return is_null() || is_string() || is_boolean() || is_number() || is_binary();
@@ -1375,7 +1371,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether type is structured /// @brief return whether type is structured
/// @sa https://json.nlohmann.me/api/basic_json/is_structured/ /// @sa https://json.nlohmann.me/api/basic_json/is_structured/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_structured() const noexcept constexpr bool is_structured() const noexcept
{ {
return is_array() || is_object(); return is_array() || is_object();
@@ -1383,7 +1378,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is null /// @brief return whether value is null
/// @sa https://json.nlohmann.me/api/basic_json/is_null/ /// @sa https://json.nlohmann.me/api/basic_json/is_null/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_null() const noexcept constexpr bool is_null() const noexcept
{ {
return m_data.m_type == value_t::null; return m_data.m_type == value_t::null;
@@ -1391,7 +1385,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a boolean /// @brief return whether value is a boolean
/// @sa https://json.nlohmann.me/api/basic_json/is_boolean/ /// @sa https://json.nlohmann.me/api/basic_json/is_boolean/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_boolean() const noexcept constexpr bool is_boolean() const noexcept
{ {
return m_data.m_type == value_t::boolean; return m_data.m_type == value_t::boolean;
@@ -1399,7 +1392,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a number /// @brief return whether value is a number
/// @sa https://json.nlohmann.me/api/basic_json/is_number/ /// @sa https://json.nlohmann.me/api/basic_json/is_number/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number() const noexcept constexpr bool is_number() const noexcept
{ {
return is_number_integer() || is_number_float(); return is_number_integer() || is_number_float();
@@ -1407,7 +1399,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an integer number /// @brief return whether value is an integer number
/// @sa https://json.nlohmann.me/api/basic_json/is_number_integer/ /// @sa https://json.nlohmann.me/api/basic_json/is_number_integer/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number_integer() const noexcept constexpr bool is_number_integer() const noexcept
{ {
return m_data.m_type == value_t::number_integer || m_data.m_type == value_t::number_unsigned; return m_data.m_type == value_t::number_integer || m_data.m_type == value_t::number_unsigned;
@@ -1415,7 +1406,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an unsigned integer number /// @brief return whether value is an unsigned integer number
/// @sa https://json.nlohmann.me/api/basic_json/is_number_unsigned/ /// @sa https://json.nlohmann.me/api/basic_json/is_number_unsigned/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number_unsigned() const noexcept constexpr bool is_number_unsigned() const noexcept
{ {
return m_data.m_type == value_t::number_unsigned; return m_data.m_type == value_t::number_unsigned;
@@ -1423,7 +1413,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a floating-point number /// @brief return whether value is a floating-point number
/// @sa https://json.nlohmann.me/api/basic_json/is_number_float/ /// @sa https://json.nlohmann.me/api/basic_json/is_number_float/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number_float() const noexcept constexpr bool is_number_float() const noexcept
{ {
return m_data.m_type == value_t::number_float; return m_data.m_type == value_t::number_float;
@@ -1431,7 +1420,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an object /// @brief return whether value is an object
/// @sa https://json.nlohmann.me/api/basic_json/is_object/ /// @sa https://json.nlohmann.me/api/basic_json/is_object/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_object() const noexcept constexpr bool is_object() const noexcept
{ {
return m_data.m_type == value_t::object; return m_data.m_type == value_t::object;
@@ -1439,7 +1427,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an array /// @brief return whether value is an array
/// @sa https://json.nlohmann.me/api/basic_json/is_array/ /// @sa https://json.nlohmann.me/api/basic_json/is_array/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_array() const noexcept constexpr bool is_array() const noexcept
{ {
return m_data.m_type == value_t::array; return m_data.m_type == value_t::array;
@@ -1447,7 +1434,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a string /// @brief return whether value is a string
/// @sa https://json.nlohmann.me/api/basic_json/is_string/ /// @sa https://json.nlohmann.me/api/basic_json/is_string/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_string() const noexcept constexpr bool is_string() const noexcept
{ {
return m_data.m_type == value_t::string; return m_data.m_type == value_t::string;
@@ -1455,7 +1441,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a binary array /// @brief return whether value is a binary array
/// @sa https://json.nlohmann.me/api/basic_json/is_binary/ /// @sa https://json.nlohmann.me/api/basic_json/is_binary/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_binary() const noexcept constexpr bool is_binary() const noexcept
{ {
return m_data.m_type == value_t::binary; return m_data.m_type == value_t::binary;
@@ -1463,7 +1448,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is discarded /// @brief return whether value is discarded
/// @sa https://json.nlohmann.me/api/basic_json/is_discarded/ /// @sa https://json.nlohmann.me/api/basic_json/is_discarded/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_discarded() const noexcept constexpr bool is_discarded() const noexcept
{ {
return m_data.m_type == value_t::discarded; return m_data.m_type == value_t::discarded;
@@ -2795,7 +2779,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief returns the number of occurrences of a key in a JSON object /// @brief returns the number of occurrences of a key in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/count/ /// @sa https://json.nlohmann.me/api/basic_json/count/
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type count(const typename object_t::key_type& key) const size_type count(const typename object_t::key_type& key) const
{ {
// return 0 for all nonobject types // return 0 for all nonobject types
@@ -2806,7 +2789,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/count/ /// @sa https://json.nlohmann.me/api/basic_json/count/
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0> detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type count(KeyType && key) const size_type count(KeyType && key) const
{ {
// return 0 for all nonobject types // return 0 for all nonobject types
@@ -2815,7 +2797,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief check the existence of an element in a JSON object /// @brief check the existence of an element in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/contains/ /// @sa https://json.nlohmann.me/api/basic_json/contains/
JSON_HEDLEY_WARN_UNUSED_RESULT
bool contains(const typename object_t::key_type& key) const bool contains(const typename object_t::key_type& key) const
{ {
return is_object() && m_data.m_value.object->find(key) != m_data.m_value.object->end(); return is_object() && m_data.m_value.object->find(key) != m_data.m_value.object->end();
@@ -2825,7 +2806,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/contains/ /// @sa https://json.nlohmann.me/api/basic_json/contains/
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0> detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
bool contains(KeyType && key) const bool contains(KeyType && key) const
{ {
return is_object() && m_data.m_value.object->find(std::forward<KeyType>(key)) != m_data.m_value.object->end(); return is_object() && m_data.m_value.object->find(std::forward<KeyType>(key)) != m_data.m_value.object->end();
@@ -2833,14 +2813,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief check the existence of an element in a JSON object given a JSON pointer /// @brief check the existence of an element in a JSON object given a JSON pointer
/// @sa https://json.nlohmann.me/api/basic_json/contains/ /// @sa https://json.nlohmann.me/api/basic_json/contains/
JSON_HEDLEY_WARN_UNUSED_RESULT
bool contains(const json_pointer& ptr) const bool contains(const json_pointer& ptr) const
{ {
return ptr.contains(this); return ptr.contains(this);
} }
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_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)
bool contains(const typename ::nlohmann::json_pointer<BasicJsonType>& ptr) const bool contains(const typename ::nlohmann::json_pointer<BasicJsonType>& ptr) const
{ {
@@ -2996,7 +2974,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief checks whether the container is empty. /// @brief checks whether the container is empty.
/// @sa https://json.nlohmann.me/api/basic_json/empty/ /// @sa https://json.nlohmann.me/api/basic_json/empty/
JSON_HEDLEY_WARN_UNUSED_RESULT
bool empty() const noexcept bool empty() const noexcept
{ {
switch (m_data.m_type) switch (m_data.m_type)
@@ -3036,7 +3013,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief returns the number of elements /// @brief returns the number of elements
/// @sa https://json.nlohmann.me/api/basic_json/size/ /// @sa https://json.nlohmann.me/api/basic_json/size/
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type size() const noexcept size_type size() const noexcept
{ {
switch (m_data.m_type) switch (m_data.m_type)
@@ -3076,7 +3052,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief returns the maximum possible number of elements /// @brief returns the maximum possible number of elements
/// @sa https://json.nlohmann.me/api/basic_json/max_size/ /// @sa https://json.nlohmann.me/api/basic_json/max_size/
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type max_size() const noexcept size_type max_size() const noexcept
{ {
switch (m_data.m_type) switch (m_data.m_type)
@@ -3598,7 +3573,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
{ {
using std::swap; using std::swap;
swap(*(m_data.m_value.array), other); swap(*(m_data.m_value.array), other);
set_parents();
} }
else else
{ {
@@ -3615,7 +3589,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
{ {
using std::swap; using std::swap;
swap(*(m_data.m_value.object), other); swap(*(m_data.m_value.object), other);
set_parents();
} }
else else
{ {
@@ -3797,13 +3770,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
bool operator==(const_reference rhs) const noexcept bool operator==(const_reference rhs) const noexcept
{ {
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_PUSH #pragma GCC diagnostic push
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal") #pragma GCC diagnostic ignored "-Wfloat-equal"
#endif #endif
const_reference lhs = *this; const_reference lhs = *this;
JSON_IMPLEMENT_OPERATOR( ==, true, false, false) JSON_IMPLEMENT_OPERATOR( ==, true, false, false)
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP #pragma GCC diagnostic pop
#endif #endif
} }
@@ -3890,12 +3863,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
friend bool operator==(const_reference lhs, const_reference rhs) noexcept friend bool operator==(const_reference lhs, const_reference rhs) noexcept
{ {
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_PUSH #pragma GCC diagnostic push
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal") #pragma GCC diagnostic ignored "-Wfloat-equal"
#endif #endif
JSON_IMPLEMENT_OPERATOR( ==, true, false, false) JSON_IMPLEMENT_OPERATOR( ==, true, false, false)
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP #pragma GCC diagnostic pop
#endif #endif
} }
@@ -4156,24 +4129,22 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief check if the input is valid JSON /// @brief check if the input is valid JSON
/// @sa https://json.nlohmann.me/api/basic_json/accept/ /// @sa https://json.nlohmann.me/api/basic_json/accept/
template<typename InputType> template<typename InputType>
JSON_HEDLEY_WARN_UNUSED_RESULT
static bool accept(InputType&& i, static bool accept(InputType&& i,
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true); return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true);
} }
/// @brief check if the input is valid JSON (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief check if the input is valid JSON (iterator pair, or iterator+sentinel pair for C++20 ranges support)
/// @sa https://json.nlohmann.me/api/basic_json/accept/ /// @sa https://json.nlohmann.me/api/basic_json/accept/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename IteratorType, typename SentinelType = IteratorType,
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0> detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
static bool accept(IteratorType first, SentinelType last, static bool accept(IteratorType first, SentinelType last,
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
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).accept(true);
} }
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
@@ -4182,7 +4153,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true); return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true);
} }
/// @brief generate SAX events /// @brief generate SAX events
@@ -4268,7 +4239,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return the type as string /// @brief return the type as string
/// @sa https://json.nlohmann.me/api/basic_json/type_name/ /// @sa https://json.nlohmann.me/api/basic_json/type_name/
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_RETURNS_NON_NULL JSON_HEDLEY_RETURNS_NON_NULL
const char* type_name() const noexcept const char* type_name() const noexcept
{ {
@@ -4969,36 +4939,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// note erase performs range check // note erase performs range check
parent.erase(json_pointer::template array_index<basic_json_t>(last_path)); parent.erase(json_pointer::template array_index<basic_json_t>(last_path));
} }
else
{
// the parent of a "remove" target must be an object or array
// (see #5396)
JSON_THROW(out_of_range::create(413, detail::concat("cannot remove value: the JSON Patch 'remove' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent));
}
};
// RFC 6902 (section 4.4) forbids "from" from being a proper prefix
// of "path" for a "move" operation: a location cannot be moved into
// one of its own children. Compares reference tokens (already
// unescaped by json_pointer's parser) rather than the raw pointer
// strings, since a token may itself contain an escaped '/' or '~'
// that would defeat a naive string-prefix comparison. "from" equal
// to "path" is *not* a proper prefix and must return false.
const auto is_proper_prefix = [](const json_pointer & from, const json_pointer & to)
{
const auto from_size = from.reference_tokens.size();
if (from_size >= to.reference_tokens.size())
{
return false;
}
for (std::size_t i = 0; i < from_size; ++i)
{
if (!(from.reference_tokens[i] == to.reference_tokens[i]))
{
return false;
}
}
return true;
}; };
// type check: top level value must be an array // type check: top level value must be an array
@@ -5076,11 +5016,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
const auto from_path = get_value("move", "from", true).template get<string_t>(); const auto from_path = get_value("move", "from", true).template get<string_t>();
json_pointer from_ptr(from_path); json_pointer from_ptr(from_path);
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
{
JSON_THROW(out_of_range::create(414, detail::concat("cannot move value: 'from' path '", from_path, "' is a proper prefix of 'path' '", path, "'"), &result));
}
// the "from" location must exist - use at() // the "from" location must exist - use at()
basic_json const v = result.at(from_ptr); basic_json const v = result.at(from_ptr);
@@ -5222,139 +5157,34 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
case value_t::object: case value_t::object:
{ {
// first pass: record, for every source key, whether it is // first pass: traverse this object's elements
// 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) for (auto it = source.cbegin(); it != source.cend(); ++it)
{ {
// escape the key name to be used in a JSON patch
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
if (target.find(it.key()) != target.end()) if (target.find(it.key()) != target.end())
{ {
common_keys_source_order.push_back(it.key()); // recursive call to compare object values at key it
auto temp_diff = diff(it.value(), target[it.key()], path_key);
result.insert(result.end(), temp_diff.begin(), temp_diff.end());
}
else
{
// found a key that is not in o -> remove it
result.push_back(object(
{
{"op", "remove"}, {"path", path_key}
}));
} }
} }
// second pass: find keys that were added (i.e., in target but // second pass: traverse other object's elements
// 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);
bool new_keys_form_suffix = true;
bool seen_new_key = false;
for (auto it = target.cbegin(); it != target.cend(); ++it) for (auto it = target.cbegin(); it != target.cend(); ++it)
{ {
if (source.find(it.key()) == source.end()) if (source.find(it.key()) == source.end())
{ {
seen_new_key = true; // found a key that is not in this -> add it
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
added_ops.push_back(
{
{"op", "add"}, {"path", path_key},
{"value", 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 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)
{
if (common_it != common_keys_source_order.cend() && it.key() == *common_it)
{
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
auto temp_diff = diff(it.value(), target[it.key()], path_key);
result.insert(result.end(), temp_diff.begin(), temp_diff.end());
++common_it;
}
else
{
// found a key that is not in target -> remove it
const auto path_key = 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
// during the pass over target -- no second source.find()
// per target key needed
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())); const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
result.push_back( result.push_back(
{ {
+1 -4
View File
@@ -17,7 +17,7 @@
#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE
#undef JSON_HEDLEY_CLANG_HAS_BUILTIN #undef JSON_HEDLEY_CLANG_HAS_BUILTIN
#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE
#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE
#undef JSON_HEDLEY_CLANG_HAS_EXTENSION #undef JSON_HEDLEY_CLANG_HAS_EXTENSION
#undef JSON_HEDLEY_CLANG_HAS_FEATURE #undef JSON_HEDLEY_CLANG_HAS_FEATURE
#undef JSON_HEDLEY_CLANG_HAS_WARNING #undef JSON_HEDLEY_CLANG_HAS_WARNING
@@ -108,10 +108,7 @@
#undef JSON_HEDLEY_PELLES_VERSION_CHECK #undef JSON_HEDLEY_PELLES_VERSION_CHECK
#undef JSON_HEDLEY_PGI_VERSION #undef JSON_HEDLEY_PGI_VERSION
#undef JSON_HEDLEY_PGI_VERSION_CHECK #undef JSON_HEDLEY_PGI_VERSION_CHECK
#undef JSON_HEDLEY_PRAGMA
#undef JSON_HEDLEY_PREDICT #undef JSON_HEDLEY_PREDICT
#undef JSON_HEDLEY_PREDICT_FALSE
#undef JSON_HEDLEY_PREDICT_TRUE
#undef JSON_HEDLEY_PRINTF_FORMAT #undef JSON_HEDLEY_PRINTF_FORMAT
#undef JSON_HEDLEY_PRIVATE #undef JSON_HEDLEY_PRIVATE
#undef JSON_HEDLEY_PUBLIC #undef JSON_HEDLEY_PUBLIC
+49 -374
View File
@@ -4945,8 +4945,8 @@ NLOHMANN_JSON_NAMESPACE_END
// code stumbling over this. See https://github.com/nlohmann/json/issues/4087 // code stumbling over this. See https://github.com/nlohmann/json/issues/4087
// for a discussion. // for a discussion.
#if defined(__clang__) #if defined(__clang__)
JSON_HEDLEY_DIAGNOSTIC_PUSH #pragma clang diagnostic push
JSON_HEDLEY_PRAGMA(clang diagnostic ignored "-Wweak-vtables") #pragma clang diagnostic ignored "-Wweak-vtables"
#endif #endif
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
@@ -5199,7 +5199,7 @@ class other_error : public exception
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
#if defined(__clang__) #if defined(__clang__)
JSON_HEDLEY_DIAGNOSTIC_POP #pragma clang diagnostic pop
#endif #endif
// #include <nlohmann/detail/macro_scope.hpp> // #include <nlohmann/detail/macro_scope.hpp>
@@ -5693,17 +5693,6 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
} }
} }
template<typename ConstructibleObjectType>
auto from_json_object_reserve(ConstructibleObjectType& obj, typename ConstructibleObjectType::size_type size, priority_tag<1> /*unused*/)
-> decltype(obj.reserve(size), void())
{
obj.reserve(size);
}
template<typename ConstructibleObjectType>
inline void from_json_object_reserve(ConstructibleObjectType& /*obj*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
{}
template<typename BasicJsonType, typename ConstructibleObjectType, template<typename BasicJsonType, typename ConstructibleObjectType,
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0> enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
@@ -5715,7 +5704,6 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
ConstructibleObjectType ret; ConstructibleObjectType ret;
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>(); const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {});
for (const auto& p : *inner_object) for (const auto& p : *inner_object)
{ {
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>()); ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
@@ -5987,8 +5975,6 @@ NLOHMANN_JSON_NAMESPACE_END
// #include <nlohmann/detail/abi_macros.hpp> // #include <nlohmann/detail/abi_macros.hpp>
// #include <nlohmann/detail/macro_scope.hpp>
// #include <nlohmann/detail/meta/type_traits.hpp> // #include <nlohmann/detail/meta/type_traits.hpp>
// #include <nlohmann/detail/string_utils.hpp> // #include <nlohmann/detail/string_utils.hpp>
@@ -6218,10 +6204,10 @@ NLOHMANN_JSON_NAMESPACE_END
namespace std namespace std
{ {
// Fix: https://github.com/nlohmann/json/issues/1401
#if defined(__clang__) #if defined(__clang__)
JSON_HEDLEY_DIAGNOSTIC_PUSH // Fix: https://github.com/nlohmann/json/issues/1401
JSON_HEDLEY_PRAGMA(clang diagnostic ignored "-Wmismatched-tags") #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmismatched-tags"
#endif #endif
template<typename IteratorType> template<typename IteratorType>
class tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>> // NOLINT(cert-dcl58-cpp) class tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>> // NOLINT(cert-dcl58-cpp)
@@ -6236,7 +6222,7 @@ class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >>
::nlohmann::detail::iteration_proxy_value<IteratorType >> ())); ::nlohmann::detail::iteration_proxy_value<IteratorType >> ()));
}; };
#if defined(__clang__) #if defined(__clang__)
JSON_HEDLEY_DIAGNOSTIC_POP #pragma clang diagnostic pop
#endif #endif
} // namespace std } // namespace std
@@ -7946,11 +7932,10 @@ class lexer : public lexer_base<BasicJsonType>
public: public:
using token_type = typename lexer_base<BasicJsonType>::token_type; using token_type = typename lexer_base<BasicJsonType>::token_type;
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = 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())) , decimal_point_char(static_cast<char_int_type>(get_decimal_point()))
, discard_number_values(discard_number_values_)
{} {}
// deleted because of pointer members // deleted because of pointer members
@@ -9077,58 +9062,6 @@ scan_number_done:
// we are done scanning a number) // we are done scanning a number)
unget(); unget();
// If the caller does not need the converted value (only whether the
// input is syntactically valid; see json_sax_acceptor/accept()), an
// unsigned/integer token can be reported without calling
// strtoull()/strtoll() at all, *provided* we can already tell from
// the digit count alone that the conversion cannot overflow 64 bits.
// Such tokens are always finite and are accepted unconditionally by
// the parser regardless of their actual value (parser::sax_parse_internal()
// never checks finiteness for value_unsigned/value_integer), so the
// classification below is all that is needed.
//
// A decimal number with up to 18 digits is always representable in
// both std::uint64_t and std::int64_t (18 nines is ~1e18, well below
// both UINT64_MAX ~1.8e19 and INT64_MAX ~9.2e18), so strtoull()/strtoll()
// could not have set errno to ERANGE for it. Numbers with more digits
// (rare in practice) fall through to the exact code below, unchanged,
// so their handling -- including reclassification to value_float when
// the value overflows 64 bits, and rejection when it is not even
// finite as a double -- is bit-for-bit identical to before this
// optimization.
//
// Note this reasons about std::uint64_t/std::int64_t, not about
// number_unsigned_t/number_integer_t (BasicJsonType's own, possibly
// narrower, template parameters -- e.g. std::uint32_t). That is fine
// *only* because discard_number_values is exclusively set by
// accept() (see json.hpp), and accept() always parses through the
// library's own json_sax_acceptor -- never a user-supplied SAX
// consumer -- whose number_unsigned()/number_integer()/number_float()
// callbacks unconditionally discard their argument and return true.
// So for every caller that can reach this branch, neither the token
// classification below nor the eventual (possibly narrowed, and on
// this fast path left stale/unset) value_unsigned/value_integer is
// ever consulted -- an unsigned/integer token is accepted outright,
// and even a >18-digit token that this fast path deliberately falls
// through for is, once reclassified to value_float, still finite
// (and thus accepted) for any digit count that fits in number_unsigned_t
// or number_integer_t regardless of that type's width. If this
// function is ever taught to run with discard_number_values true for
// a caller that *does* read the converted value, this reasoning (and
// the fast path below) would need to be revisited.
if (discard_number_values)
{
constexpr std::size_t safe_digit_count = 18;
if (number_type == token_type::value_unsigned && token_buffer.size() <= safe_digit_count)
{
return token_type::value_unsigned;
}
if (number_type == token_type::value_integer && token_buffer.size() - 1 <= safe_digit_count)
{
return token_type::value_integer;
}
}
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg) char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
errno = 0; errno = 0;
@@ -9243,7 +9176,8 @@ scan_number_done:
*/ */
char_int_type get() char_int_type get()
{ {
advance_position(); ++position.chars_read_total;
++position.chars_read_current_line;
if (next_unget) if (next_unget)
{ {
@@ -9255,23 +9189,6 @@ scan_number_done:
current = ia.get_character(); current = ia.get_character();
} }
return track_after_read();
}
/// shared head of get() / get_ignoring_pending_unget(): bump the
/// per-character position counters (line-count-on-'\n' bookkeeping is
/// handled afterwards, in track_after_read(), once `current` is known)
void advance_position() noexcept
{
++position.chars_read_total;
++position.chars_read_current_line;
}
/// shared tail of get() / get_ignoring_pending_unget(): capture the
/// character for error messages (if needed) and update line/column
/// bookkeeping for the character now in `current`
char_int_type track_after_read()
{
// seekable adapters reconstruct the token lazily on error (see // seekable adapters reconstruct the token lazily on error (see
// get_token_string), so the eager per-character copy is skipped // get_token_string), so the eager per-character copy is skipped
capture_char(std::integral_constant<bool, lazy_token_string> {}); capture_char(std::integral_constant<bool, lazy_token_string> {});
@@ -9285,29 +9202,6 @@ scan_number_done:
return current; return current;
} }
/*!
@brief like get(), but for call sites that can prove no unget() is pending
get() has to check the `next_unget` flag on every call, because a
previous token may have ended with unget() (e.g. scan_number() always
ungets the character that terminated the number, so the next call to
scan() can see it again). skip_whitespace() reads that first,
possibly-ungotten character via a plain get(), but every further
character it reads is guaranteed to be a fresh read: nothing between
those calls invokes unget(). This variant skips the (otherwise always
false) next_unget branch for those calls; it is not a general
replacement for get().
*/
char_int_type get_ignoring_pending_unget()
{
JSON_ASSERT(!next_unget);
advance_position();
current = ia.get_character();
return track_after_read();
}
/// seekable adapter: nothing to capture, the token is rebuilt on error /// seekable adapter: nothing to capture, the token is rebuilt on error
void capture_char(std::true_type /*lazy*/) const noexcept {} void capture_char(std::true_type /*lazy*/) const noexcept {}
@@ -9501,37 +9395,13 @@ scan_number_done:
return true; return true;
} }
/// whether `current` is one of the four JSON whitespace characters
bool current_is_whitespace() const noexcept
{
return current == ' ' || current == '\t' || current == '\n' || current == '\r';
}
void skip_whitespace() void skip_whitespace()
{ {
// the first character may be a pending unget() left over from the
// previous token (see get_ignoring_pending_unget()); every
// subsequent character read by this loop is guaranteed fresh, since
// nothing below calls unget()
get();
if (!current_is_whitespace())
{
return;
}
// this is written as an if-guarded do-while (rather than a plain
// while loop) because that shape is what lets both GCC and Clang
// keep the input adapter's read pointer in a register across
// iterations; the equivalent while-loop measurably defeated that
// optimization in testing, turning long whitespace runs (e.g. the
// indentation of pretty-printed JSON) from a register-only loop
// into one that reloads the pointer from memory every character
do do
{ {
get_ignoring_pending_unget(); get();
} }
while (current_is_whitespace()); while (current == ' ' || current == '\t' || current == '\n' || current == '\r');
} }
token_type scan() token_type scan()
@@ -9667,13 +9537,6 @@ scan_number_done:
const char_int_type decimal_point_char = '.'; const char_int_type decimal_point_char = '.';
/// the position of the decimal point in the input /// 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
/// token classification and never looks at the converted numeric value;
/// when set, scan_number() may skip strtoull()/strtoll() for
/// value_unsigned/value_integer tokens whose digit count guarantees they
/// fit into 64 bits (see scan_number())
const bool discard_number_values = false;
}; };
} // namespace detail } // namespace detail
@@ -14180,10 +14043,9 @@ class parser
parser_callback_t<BasicJsonType> cb = nullptr, parser_callback_t<BasicJsonType> cb = nullptr,
const bool allow_exceptions_ = true, const bool allow_exceptions_ = true,
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas_ = false, const bool ignore_trailing_commas_ = false)
const bool discard_number_values_ = false)
: callback(std::move(cb)) : callback(std::move(cb))
, m_lexer(std::move(adapter), ignore_comments, discard_number_values_) , m_lexer(std::move(adapter), ignore_comments)
, allow_exceptions(allow_exceptions_) , allow_exceptions(allow_exceptions_)
, ignore_trailing_commas(ignore_trailing_commas_) , ignore_trailing_commas(ignore_trailing_commas_)
{ {
@@ -16532,20 +16394,6 @@ class json_pointer
} }
} }
// the reference token consists only of digits at this point (cf. checks
// above); however, its numeric value might not be representable, in which
// case array_index() would throw out_of_range.404/410 -- contains() must
// not throw (see #5395), so such a reference token is treated as "not found"
errno = 0; // strtoull() does not reset errno on success
char* p_end = nullptr; // NOLINT(misc-const-correctness)
const unsigned long long magnitude = std::strtoull(reference_token.c_str(), &p_end, 10); // NOLINT(runtime/int)
if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX
|| magnitude >= static_cast<unsigned long long>((std::numeric_limits<typename BasicJsonType::size_type>::max)()))) // NOLINT(runtime/int)
{
// the array index cannot be represented as size_type
return false;
}
const auto idx = array_index<BasicJsonType>(reference_token); const auto idx = array_index<BasicJsonType>(reference_token);
if (idx >= ptr->size()) if (idx >= ptr->size())
{ {
@@ -19010,8 +18858,8 @@ class binary_writer
void write_compact_float(const number_float_t n, detail::input_format_t format) void write_compact_float(const number_float_t n, detail::input_format_t format)
{ {
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_PUSH #pragma GCC diagnostic push
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal") #pragma GCC diagnostic ignored "-Wfloat-equal"
#endif #endif
if (!std::isfinite(n) || ((static_cast<double>(n) >= static_cast<double>(std::numeric_limits<float>::lowest()) && if (!std::isfinite(n) || ((static_cast<double>(n) >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
static_cast<double>(n) <= static_cast<double>((std::numeric_limits<float>::max)()) && static_cast<double>(n) <= static_cast<double>((std::numeric_limits<float>::max)()) &&
@@ -19030,7 +18878,7 @@ class binary_writer
write_number(n); write_number(n);
} }
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP #pragma GCC diagnostic pop
#endif #endif
} }
@@ -20205,8 +20053,8 @@ char* to_chars(char* first, const char* last, FloatType value)
} }
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_PUSH #pragma GCC diagnostic push
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal") #pragma GCC diagnostic ignored "-Wfloat-equal"
#endif #endif
if (value == 0) // +-0 if (value == 0) // +-0
{ {
@@ -20217,7 +20065,7 @@ char* to_chars(char* first, const char* last, FloatType value)
return first; return first;
} }
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP #pragma GCC diagnostic pop
#endif #endif
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10); JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10);
@@ -21744,12 +21592,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
detail::parser_callback_t<basic_json>cb = nullptr, detail::parser_callback_t<basic_json>cb = nullptr,
const bool allow_exceptions = true, const bool allow_exceptions = true,
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false, const bool ignore_trailing_commas = false
const bool discard_number_values = false
) )
{ {
return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter), return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter),
std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas, discard_number_values); std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
} }
private: private:
@@ -22916,7 +22763,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief serialization /// @brief serialization
/// @sa https://json.nlohmann.me/api/basic_json/dump/ /// @sa https://json.nlohmann.me/api/basic_json/dump/
JSON_HEDLEY_WARN_UNUSED_RESULT
string_t dump(const int indent = -1, string_t dump(const int indent = -1,
const char indent_char = ' ', const char indent_char = ' ',
const bool ensure_ascii = false, const bool ensure_ascii = false,
@@ -22939,7 +22785,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return the type of the JSON value (explicit) /// @brief return the type of the JSON value (explicit)
/// @sa https://json.nlohmann.me/api/basic_json/type/ /// @sa https://json.nlohmann.me/api/basic_json/type/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr value_t type() const noexcept constexpr value_t type() const noexcept
{ {
return m_data.m_type; return m_data.m_type;
@@ -22947,7 +22792,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether type is primitive /// @brief return whether type is primitive
/// @sa https://json.nlohmann.me/api/basic_json/is_primitive/ /// @sa https://json.nlohmann.me/api/basic_json/is_primitive/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_primitive() const noexcept constexpr bool is_primitive() const noexcept
{ {
return is_null() || is_string() || is_boolean() || is_number() || is_binary(); return is_null() || is_string() || is_boolean() || is_number() || is_binary();
@@ -22955,7 +22799,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether type is structured /// @brief return whether type is structured
/// @sa https://json.nlohmann.me/api/basic_json/is_structured/ /// @sa https://json.nlohmann.me/api/basic_json/is_structured/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_structured() const noexcept constexpr bool is_structured() const noexcept
{ {
return is_array() || is_object(); return is_array() || is_object();
@@ -22963,7 +22806,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is null /// @brief return whether value is null
/// @sa https://json.nlohmann.me/api/basic_json/is_null/ /// @sa https://json.nlohmann.me/api/basic_json/is_null/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_null() const noexcept constexpr bool is_null() const noexcept
{ {
return m_data.m_type == value_t::null; return m_data.m_type == value_t::null;
@@ -22971,7 +22813,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a boolean /// @brief return whether value is a boolean
/// @sa https://json.nlohmann.me/api/basic_json/is_boolean/ /// @sa https://json.nlohmann.me/api/basic_json/is_boolean/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_boolean() const noexcept constexpr bool is_boolean() const noexcept
{ {
return m_data.m_type == value_t::boolean; return m_data.m_type == value_t::boolean;
@@ -22979,7 +22820,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a number /// @brief return whether value is a number
/// @sa https://json.nlohmann.me/api/basic_json/is_number/ /// @sa https://json.nlohmann.me/api/basic_json/is_number/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number() const noexcept constexpr bool is_number() const noexcept
{ {
return is_number_integer() || is_number_float(); return is_number_integer() || is_number_float();
@@ -22987,7 +22827,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an integer number /// @brief return whether value is an integer number
/// @sa https://json.nlohmann.me/api/basic_json/is_number_integer/ /// @sa https://json.nlohmann.me/api/basic_json/is_number_integer/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number_integer() const noexcept constexpr bool is_number_integer() const noexcept
{ {
return m_data.m_type == value_t::number_integer || m_data.m_type == value_t::number_unsigned; return m_data.m_type == value_t::number_integer || m_data.m_type == value_t::number_unsigned;
@@ -22995,7 +22834,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an unsigned integer number /// @brief return whether value is an unsigned integer number
/// @sa https://json.nlohmann.me/api/basic_json/is_number_unsigned/ /// @sa https://json.nlohmann.me/api/basic_json/is_number_unsigned/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number_unsigned() const noexcept constexpr bool is_number_unsigned() const noexcept
{ {
return m_data.m_type == value_t::number_unsigned; return m_data.m_type == value_t::number_unsigned;
@@ -23003,7 +22841,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a floating-point number /// @brief return whether value is a floating-point number
/// @sa https://json.nlohmann.me/api/basic_json/is_number_float/ /// @sa https://json.nlohmann.me/api/basic_json/is_number_float/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number_float() const noexcept constexpr bool is_number_float() const noexcept
{ {
return m_data.m_type == value_t::number_float; return m_data.m_type == value_t::number_float;
@@ -23011,7 +22848,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an object /// @brief return whether value is an object
/// @sa https://json.nlohmann.me/api/basic_json/is_object/ /// @sa https://json.nlohmann.me/api/basic_json/is_object/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_object() const noexcept constexpr bool is_object() const noexcept
{ {
return m_data.m_type == value_t::object; return m_data.m_type == value_t::object;
@@ -23019,7 +22855,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an array /// @brief return whether value is an array
/// @sa https://json.nlohmann.me/api/basic_json/is_array/ /// @sa https://json.nlohmann.me/api/basic_json/is_array/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_array() const noexcept constexpr bool is_array() const noexcept
{ {
return m_data.m_type == value_t::array; return m_data.m_type == value_t::array;
@@ -23027,7 +22862,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a string /// @brief return whether value is a string
/// @sa https://json.nlohmann.me/api/basic_json/is_string/ /// @sa https://json.nlohmann.me/api/basic_json/is_string/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_string() const noexcept constexpr bool is_string() const noexcept
{ {
return m_data.m_type == value_t::string; return m_data.m_type == value_t::string;
@@ -23035,7 +22869,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a binary array /// @brief return whether value is a binary array
/// @sa https://json.nlohmann.me/api/basic_json/is_binary/ /// @sa https://json.nlohmann.me/api/basic_json/is_binary/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_binary() const noexcept constexpr bool is_binary() const noexcept
{ {
return m_data.m_type == value_t::binary; return m_data.m_type == value_t::binary;
@@ -23043,7 +22876,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is discarded /// @brief return whether value is discarded
/// @sa https://json.nlohmann.me/api/basic_json/is_discarded/ /// @sa https://json.nlohmann.me/api/basic_json/is_discarded/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_discarded() const noexcept constexpr bool is_discarded() const noexcept
{ {
return m_data.m_type == value_t::discarded; return m_data.m_type == value_t::discarded;
@@ -24375,7 +24207,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief returns the number of occurrences of a key in a JSON object /// @brief returns the number of occurrences of a key in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/count/ /// @sa https://json.nlohmann.me/api/basic_json/count/
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type count(const typename object_t::key_type& key) const size_type count(const typename object_t::key_type& key) const
{ {
// return 0 for all nonobject types // return 0 for all nonobject types
@@ -24386,7 +24217,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/count/ /// @sa https://json.nlohmann.me/api/basic_json/count/
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0> detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type count(KeyType && key) const size_type count(KeyType && key) const
{ {
// return 0 for all nonobject types // return 0 for all nonobject types
@@ -24395,7 +24225,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief check the existence of an element in a JSON object /// @brief check the existence of an element in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/contains/ /// @sa https://json.nlohmann.me/api/basic_json/contains/
JSON_HEDLEY_WARN_UNUSED_RESULT
bool contains(const typename object_t::key_type& key) const bool contains(const typename object_t::key_type& key) const
{ {
return is_object() && m_data.m_value.object->find(key) != m_data.m_value.object->end(); return is_object() && m_data.m_value.object->find(key) != m_data.m_value.object->end();
@@ -24405,7 +24234,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/contains/ /// @sa https://json.nlohmann.me/api/basic_json/contains/
template<class KeyType, detail::enable_if_t< template<class KeyType, detail::enable_if_t<
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0> detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
bool contains(KeyType && key) const bool contains(KeyType && key) const
{ {
return is_object() && m_data.m_value.object->find(std::forward<KeyType>(key)) != m_data.m_value.object->end(); return is_object() && m_data.m_value.object->find(std::forward<KeyType>(key)) != m_data.m_value.object->end();
@@ -24413,14 +24241,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief check the existence of an element in a JSON object given a JSON pointer /// @brief check the existence of an element in a JSON object given a JSON pointer
/// @sa https://json.nlohmann.me/api/basic_json/contains/ /// @sa https://json.nlohmann.me/api/basic_json/contains/
JSON_HEDLEY_WARN_UNUSED_RESULT
bool contains(const json_pointer& ptr) const bool contains(const json_pointer& ptr) const
{ {
return ptr.contains(this); return ptr.contains(this);
} }
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_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)
bool contains(const typename ::nlohmann::json_pointer<BasicJsonType>& ptr) const bool contains(const typename ::nlohmann::json_pointer<BasicJsonType>& ptr) const
{ {
@@ -24576,7 +24402,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief checks whether the container is empty. /// @brief checks whether the container is empty.
/// @sa https://json.nlohmann.me/api/basic_json/empty/ /// @sa https://json.nlohmann.me/api/basic_json/empty/
JSON_HEDLEY_WARN_UNUSED_RESULT
bool empty() const noexcept bool empty() const noexcept
{ {
switch (m_data.m_type) switch (m_data.m_type)
@@ -24616,7 +24441,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief returns the number of elements /// @brief returns the number of elements
/// @sa https://json.nlohmann.me/api/basic_json/size/ /// @sa https://json.nlohmann.me/api/basic_json/size/
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type size() const noexcept size_type size() const noexcept
{ {
switch (m_data.m_type) switch (m_data.m_type)
@@ -24656,7 +24480,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief returns the maximum possible number of elements /// @brief returns the maximum possible number of elements
/// @sa https://json.nlohmann.me/api/basic_json/max_size/ /// @sa https://json.nlohmann.me/api/basic_json/max_size/
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type max_size() const noexcept size_type max_size() const noexcept
{ {
switch (m_data.m_type) switch (m_data.m_type)
@@ -25178,7 +25001,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
{ {
using std::swap; using std::swap;
swap(*(m_data.m_value.array), other); swap(*(m_data.m_value.array), other);
set_parents();
} }
else else
{ {
@@ -25195,7 +25017,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
{ {
using std::swap; using std::swap;
swap(*(m_data.m_value.object), other); swap(*(m_data.m_value.object), other);
set_parents();
} }
else else
{ {
@@ -25377,13 +25198,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
bool operator==(const_reference rhs) const noexcept bool operator==(const_reference rhs) const noexcept
{ {
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_PUSH #pragma GCC diagnostic push
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal") #pragma GCC diagnostic ignored "-Wfloat-equal"
#endif #endif
const_reference lhs = *this; const_reference lhs = *this;
JSON_IMPLEMENT_OPERATOR( ==, true, false, false) JSON_IMPLEMENT_OPERATOR( ==, true, false, false)
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP #pragma GCC diagnostic pop
#endif #endif
} }
@@ -25470,12 +25291,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
friend bool operator==(const_reference lhs, const_reference rhs) noexcept friend bool operator==(const_reference lhs, const_reference rhs) noexcept
{ {
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_PUSH #pragma GCC diagnostic push
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal") #pragma GCC diagnostic ignored "-Wfloat-equal"
#endif #endif
JSON_IMPLEMENT_OPERATOR( ==, true, false, false) JSON_IMPLEMENT_OPERATOR( ==, true, false, false)
#ifdef __GNUC__ #ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_POP #pragma GCC diagnostic pop
#endif #endif
} }
@@ -25736,24 +25557,22 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief check if the input is valid JSON /// @brief check if the input is valid JSON
/// @sa https://json.nlohmann.me/api/basic_json/accept/ /// @sa https://json.nlohmann.me/api/basic_json/accept/
template<typename InputType> template<typename InputType>
JSON_HEDLEY_WARN_UNUSED_RESULT
static bool accept(InputType&& i, static bool accept(InputType&& i,
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true); return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true);
} }
/// @brief check if the input is valid JSON (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief check if the input is valid JSON (iterator pair, or iterator+sentinel pair for C++20 ranges support)
/// @sa https://json.nlohmann.me/api/basic_json/accept/ /// @sa https://json.nlohmann.me/api/basic_json/accept/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename IteratorType, typename SentinelType = IteratorType,
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0> detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
static bool accept(IteratorType first, SentinelType last, static bool accept(IteratorType first, SentinelType last,
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
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).accept(true);
} }
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
@@ -25762,7 +25581,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true); return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true);
} }
/// @brief generate SAX events /// @brief generate SAX events
@@ -25848,7 +25667,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return the type as string /// @brief return the type as string
/// @sa https://json.nlohmann.me/api/basic_json/type_name/ /// @sa https://json.nlohmann.me/api/basic_json/type_name/
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_RETURNS_NON_NULL JSON_HEDLEY_RETURNS_NON_NULL
const char* type_name() const noexcept const char* type_name() const noexcept
{ {
@@ -26549,36 +26367,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// note erase performs range check // note erase performs range check
parent.erase(json_pointer::template array_index<basic_json_t>(last_path)); parent.erase(json_pointer::template array_index<basic_json_t>(last_path));
} }
else
{
// the parent of a "remove" target must be an object or array
// (see #5396)
JSON_THROW(out_of_range::create(413, detail::concat("cannot remove value: the JSON Patch 'remove' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent));
}
};
// RFC 6902 (section 4.4) forbids "from" from being a proper prefix
// of "path" for a "move" operation: a location cannot be moved into
// one of its own children. Compares reference tokens (already
// unescaped by json_pointer's parser) rather than the raw pointer
// strings, since a token may itself contain an escaped '/' or '~'
// that would defeat a naive string-prefix comparison. "from" equal
// to "path" is *not* a proper prefix and must return false.
const auto is_proper_prefix = [](const json_pointer & from, const json_pointer & to)
{
const auto from_size = from.reference_tokens.size();
if (from_size >= to.reference_tokens.size())
{
return false;
}
for (std::size_t i = 0; i < from_size; ++i)
{
if (!(from.reference_tokens[i] == to.reference_tokens[i]))
{
return false;
}
}
return true;
}; };
// type check: top level value must be an array // type check: top level value must be an array
@@ -26656,11 +26444,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
const auto from_path = get_value("move", "from", true).template get<string_t>(); const auto from_path = get_value("move", "from", true).template get<string_t>();
json_pointer from_ptr(from_path); json_pointer from_ptr(from_path);
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
{
JSON_THROW(out_of_range::create(414, detail::concat("cannot move value: 'from' path '", from_path, "' is a proper prefix of 'path' '", path, "'"), &result));
}
// the "from" location must exist - use at() // the "from" location must exist - use at()
basic_json const v = result.at(from_ptr); basic_json const v = result.at(from_ptr);
@@ -26802,139 +26585,34 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
case value_t::object: case value_t::object:
{ {
// first pass: record, for every source key, whether it is // first pass: traverse this object's elements
// 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) for (auto it = source.cbegin(); it != source.cend(); ++it)
{ {
// escape the key name to be used in a JSON patch
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
if (target.find(it.key()) != target.end()) if (target.find(it.key()) != target.end())
{ {
common_keys_source_order.push_back(it.key()); // recursive call to compare object values at key it
auto temp_diff = diff(it.value(), target[it.key()], path_key);
result.insert(result.end(), temp_diff.begin(), temp_diff.end());
}
else
{
// found a key that is not in o -> remove it
result.push_back(object(
{
{"op", "remove"}, {"path", path_key}
}));
} }
} }
// second pass: find keys that were added (i.e., in target but // second pass: traverse other object's elements
// 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);
bool new_keys_form_suffix = true;
bool seen_new_key = false;
for (auto it = target.cbegin(); it != target.cend(); ++it) for (auto it = target.cbegin(); it != target.cend(); ++it)
{ {
if (source.find(it.key()) == source.end()) if (source.find(it.key()) == source.end())
{ {
seen_new_key = true; // found a key that is not in this -> add it
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
added_ops.push_back(
{
{"op", "add"}, {"path", path_key},
{"value", 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 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)
{
if (common_it != common_keys_source_order.cend() && it.key() == *common_it)
{
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
auto temp_diff = diff(it.value(), target[it.key()], path_key);
result.insert(result.end(), temp_diff.begin(), temp_diff.end());
++common_it;
}
else
{
// found a key that is not in target -> remove it
const auto path_key = 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
// during the pass over target -- no second source.find()
// per target key needed
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())); const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
result.push_back( result.push_back(
{ {
@@ -27287,7 +26965,7 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE
#undef JSON_HEDLEY_CLANG_HAS_BUILTIN #undef JSON_HEDLEY_CLANG_HAS_BUILTIN
#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE
#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE
#undef JSON_HEDLEY_CLANG_HAS_EXTENSION #undef JSON_HEDLEY_CLANG_HAS_EXTENSION
#undef JSON_HEDLEY_CLANG_HAS_FEATURE #undef JSON_HEDLEY_CLANG_HAS_FEATURE
#undef JSON_HEDLEY_CLANG_HAS_WARNING #undef JSON_HEDLEY_CLANG_HAS_WARNING
@@ -27378,10 +27056,7 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
#undef JSON_HEDLEY_PELLES_VERSION_CHECK #undef JSON_HEDLEY_PELLES_VERSION_CHECK
#undef JSON_HEDLEY_PGI_VERSION #undef JSON_HEDLEY_PGI_VERSION
#undef JSON_HEDLEY_PGI_VERSION_CHECK #undef JSON_HEDLEY_PGI_VERSION_CHECK
#undef JSON_HEDLEY_PRAGMA
#undef JSON_HEDLEY_PREDICT #undef JSON_HEDLEY_PREDICT
#undef JSON_HEDLEY_PREDICT_FALSE
#undef JSON_HEDLEY_PREDICT_TRUE
#undef JSON_HEDLEY_PRINTF_FORMAT #undef JSON_HEDLEY_PRINTF_FORMAT
#undef JSON_HEDLEY_PRIVATE #undef JSON_HEDLEY_PRIVATE
#undef JSON_HEDLEY_PUBLIC #undef JSON_HEDLEY_PUBLIC
-53
View File
@@ -125,51 +125,6 @@ json_test_set_test_options(test-unicode4 TEST_PROPERTIES TIMEOUT 3000)
# add unit tests # add unit tests
############################################################################# #############################################################################
# Generate the leak checks for every JSON_HEDLEY_* macro defined in
# hedley.hpp; tests/src/unit-no-macro-leak.cpp #include-s the result after
# nlohmann/json.hpp (see issue #5408). Using the shared
# cmake/scripts/gen_hedley_undef_check.cmake script (also used by `make
# update_hedley_undef`) instead of a hand-maintained list of macro names
# means this test can never go stale after a future `make update_hedley`.
set(hedley_hpp "${PROJECT_SOURCE_DIR}/include/nlohmann/thirdparty/hedley/hedley.hpp")
set(hedley_undef_check_script "${PROJECT_SOURCE_DIR}/cmake/scripts/gen_hedley_undef_check.cmake")
set(hedley_undef_checks "${PROJECT_BINARY_DIR}/include/hedley_undef_checks.inc")
# Reconfigure whenever the vendored header or the generator script changes,
# so a `cmake --build` after `make update_hedley` does not silently keep a
# stale generated file around.
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
"${hedley_hpp}"
"${hedley_undef_check_script}")
# Generate once at configure time, so the very first build (before any
# custom-command build step has run) already has an up-to-date file.
execute_process(
COMMAND ${CMAKE_COMMAND}
"-DHEDLEY_HPP=${hedley_hpp}"
"-DOUTPUT=${hedley_undef_checks}"
-DMODE=checks
-P "${hedley_undef_check_script}"
RESULT_VARIABLE hedley_undef_check_result
)
if(NOT hedley_undef_check_result EQUAL 0)
message(FATAL_ERROR "Failed to generate ${hedley_undef_checks}")
endif()
# Also (re)generate as a build step, so an incremental build after editing
# hedley.hpp without a full reconfigure still picks up the change.
add_custom_command(
OUTPUT "${hedley_undef_checks}"
COMMAND ${CMAKE_COMMAND}
"-DHEDLEY_HPP=${hedley_hpp}"
"-DOUTPUT=${hedley_undef_checks}"
-DMODE=checks
-P "${hedley_undef_check_script}"
DEPENDS "${hedley_hpp}" "${hedley_undef_check_script}"
COMMENT "Generating Hedley undef leak checks"
VERBATIM)
add_custom_target(generate_hedley_undef_checks DEPENDS "${hedley_undef_checks}")
if("${JSON_TestStandards}" STREQUAL "") if("${JSON_TestStandards}" STREQUAL "")
set(test_cxx_standards 11 14 17 20 23) set(test_cxx_standards 11 14 17 20 23)
unset(test_force) unset(test_force)
@@ -208,14 +163,6 @@ foreach(file ${files})
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}) json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
endforeach() endforeach()
# tests/src/unit-no-macro-leak.cpp #include-s the generated leak-check file,
# so its test targets must be built after generate_hedley_undef_checks.
foreach(cxx_standard ${test_cxx_standards})
if(TARGET test-no-macro-leak_cpp${cxx_standard})
add_dependencies(test-no-macro-leak_cpp${cxx_standard} generate_hedley_undef_checks)
endif()
endforeach()
if(json_32bit_test_only) if(json_32bit_test_only)
# Skip all other tests in this file # Skip all other tests in this file
return() return()
+319
View File
@@ -214,4 +214,323 @@ static void BinaryToCbor(benchmark::State& state)
} }
BENCHMARK(BinaryToCbor)->RangeMultiplier(2)->Range(8, 8 << 12); BENCHMARK(BinaryToCbor)->RangeMultiplier(2)->Range(8, 8 << 12);
//////////////////////////////////////////////////////////////////////////////
// parse binary formats
//////////////////////////////////////////////////////////////////////////////
// Only MessagePack had a read benchmark (FromMsgpack above, left untouched so
// its numbers stay comparable across releases). The benchmarks below cover the
// other formats, and read from a contiguous buffer as well as from a FILE*:
// most callers pass a container, and the two adapters compile to different
// code. The test data repository ships JSON only, so the input for each is
// derived at setup time by serializing a parsed test file.
/// binary format to benchmark; the _optimized variants add UBJSON/BJData size
/// and type annotations, which the readers handle in a separate code path
enum class binary_format
{
cbor,
msgpack,
ubjson,
ubjson_optimized,
bjdata,
bjdata_optimized,
bson
};
static std::vector<std::uint8_t> to_binary(const json& j, const binary_format format)
{
switch (format)
{
case binary_format::cbor:
return json::to_cbor(j);
case binary_format::msgpack:
return json::to_msgpack(j);
case binary_format::ubjson:
return json::to_ubjson(j);
case binary_format::ubjson_optimized:
return json::to_ubjson(j, true, true);
case binary_format::bjdata:
return json::to_bjdata(j);
case binary_format::bjdata_optimized:
return json::to_bjdata(j, true, true);
case binary_format::bson:
default:
return json::to_bson(j);
}
}
static json from_binary(const std::vector<std::uint8_t>& bytes, const binary_format format)
{
switch (format)
{
case binary_format::cbor:
return json::from_cbor(bytes);
case binary_format::msgpack:
return json::from_msgpack(bytes);
case binary_format::ubjson:
case binary_format::ubjson_optimized:
return json::from_ubjson(bytes);
case binary_format::bjdata:
case binary_format::bjdata_optimized:
return json::from_bjdata(bytes);
case binary_format::bson:
default:
return json::from_bson(bytes);
}
}
static json from_binary(std::FILE* file, const binary_format format)
{
switch (format)
{
case binary_format::cbor:
return json::from_cbor(file);
case binary_format::msgpack:
return json::from_msgpack(file);
case binary_format::ubjson:
case binary_format::ubjson_optimized:
return json::from_ubjson(file);
case binary_format::bjdata:
case binary_format::bjdata_optimized:
return json::from_bjdata(file);
case binary_format::bson:
default:
return json::from_bson(file);
}
}
/*!
@brief serialize a parsed test file to @a format
Returns an empty vector and marks the benchmark as skipped if the file cannot
be represented in the format, rather than letting the exception escape: BSON
requires an object at the top level, and several test files are arrays.
*/
static std::vector<std::uint8_t> binary_input(benchmark::State& state, const char* filename, const binary_format format)
{
std::ifstream f(filename);
std::string const str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
const json j = json::parse(str);
if (format == binary_format::bson && !j.is_object())
{
state.SkipWithError("BSON requires an object at the top level");
return {};
}
return to_binary(j, format);
}
static void FromBinaryBuffer(benchmark::State& state, const char* filename, const binary_format format)
{
const std::vector<std::uint8_t> bytes = binary_input(state, filename, format);
if (bytes.empty())
{
return;
}
for (auto _ : state)
{
// the value is destroyed outside the timed section, because destroying
// a large DOM is not what this benchmark measures
state.PauseTiming();
auto* j = new json();
state.ResumeTiming();
*j = from_binary(bytes, format);
state.PauseTiming();
delete j;
state.ResumeTiming();
}
state.SetBytesProcessed(state.iterations() * bytes.size());
}
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::cbor);
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::cbor);
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::cbor);
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::cbor);
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / floats, TEST_DATA_DIRECTORY "/regression/floats.json", binary_format::cbor);
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / signed_ints, TEST_DATA_DIRECTORY "/regression/signed_ints.json", binary_format::cbor);
BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::msgpack);
BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::msgpack);
BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::msgpack);
BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::msgpack);
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::ubjson);
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::ubjson);
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::ubjson);
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::ubjson);
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson_optimized / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::ubjson_optimized);
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson_optimized / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::ubjson_optimized);
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bjdata);
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata);
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata_optimized / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bjdata_optimized);
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata_optimized / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata_optimized);
// BSON requires an object at the top level, so the array-rooted test files
// (jeopardy and the regression files) cannot be captured here
BENCHMARK_CAPTURE(FromBinaryBuffer, bson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bson);
BENCHMARK_CAPTURE(FromBinaryBuffer, bson / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::bson);
BENCHMARK_CAPTURE(FromBinaryBuffer, bson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bson);
static void FromBinaryFile(benchmark::State& state, const char* filename, const binary_format format)
{
const std::vector<std::uint8_t> bytes = binary_input(state, filename, format);
if (bytes.empty())
{
return;
}
const char* tmp = "benchmark_input.bin";
std::ofstream o(tmp, std::ios::binary);
o.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
o.flush();
o.close();
for (auto _ : state)
{
state.PauseTiming();
auto* j = new json();
auto* file = std::fopen(tmp, "rb");
state.ResumeTiming();
*j = from_binary(file, format);
state.PauseTiming();
std::fclose(file);
delete j;
state.ResumeTiming();
}
state.SetBytesProcessed(state.iterations() * bytes.size());
}
BENCHMARK_CAPTURE(FromBinaryFile, cbor / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::cbor);
BENCHMARK_CAPTURE(FromBinaryFile, cbor / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::cbor);
BENCHMARK_CAPTURE(FromBinaryFile, ubjson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::ubjson);
BENCHMARK_CAPTURE(FromBinaryFile, ubjson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::ubjson);
BENCHMARK_CAPTURE(FromBinaryFile, bjdata / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata);
BENCHMARK_CAPTURE(FromBinaryFile, bson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bson);
//////////////////////////////////////////////////////////////////////////////
// parse binary formats: value shapes
//////////////////////////////////////////////////////////////////////////////
// The test files above are wide and shallow, but the readers' cost is per
// container, so these cover the shapes that stress the container handling
// itself. Every shape is wrapped in an object so that BSON, which requires an
// object at the top level, measures the same value as the other formats.
/// deeply nested arrays: one container per level, no other work
static json make_nested()
{
json nested = json::array();
json* p = &nested;
for (std::size_t i = 1; i < 1000; ++i)
{
p->push_back(json::array());
p = &p->operator[](0);
}
json j = json::object();
j["data"] = std::move(nested);
return j;
}
/// many sibling containers: maximum container churn, minimum nesting
static json make_containers()
{
json data = json::array();
for (std::size_t i = 0; i < 100000; ++i)
{
data.push_back(json::array({1, 2}));
}
json j = json::object();
j["data"] = std::move(data);
return j;
}
/// one flat array of numbers: the scalar decoding path, which must not move
static json make_scalars()
{
json data = json::array();
for (std::size_t i = 0; i < 1000000; ++i)
{
data.push_back(i);
}
json j = json::object();
j["data"] = std::move(data);
return j;
}
static void FromBinaryShape(benchmark::State& state, json (*build)(), const binary_format format)
{
const std::vector<std::uint8_t> bytes = to_binary(build(), format);
for (auto _ : state)
{
state.PauseTiming();
auto* j = new json();
state.ResumeTiming();
*j = from_binary(bytes, format);
state.PauseTiming();
delete j;
state.ResumeTiming();
}
state.SetBytesProcessed(state.iterations() * bytes.size());
}
BENCHMARK_CAPTURE(FromBinaryShape, nested / cbor, make_nested, binary_format::cbor);
BENCHMARK_CAPTURE(FromBinaryShape, nested / msgpack, make_nested, binary_format::msgpack);
BENCHMARK_CAPTURE(FromBinaryShape, nested / ubjson, make_nested, binary_format::ubjson);
BENCHMARK_CAPTURE(FromBinaryShape, nested / bjdata, make_nested, binary_format::bjdata);
BENCHMARK_CAPTURE(FromBinaryShape, nested / bson, make_nested, binary_format::bson);
BENCHMARK_CAPTURE(FromBinaryShape, containers / cbor, make_containers, binary_format::cbor);
BENCHMARK_CAPTURE(FromBinaryShape, containers / msgpack, make_containers, binary_format::msgpack);
BENCHMARK_CAPTURE(FromBinaryShape, containers / ubjson, make_containers, binary_format::ubjson);
BENCHMARK_CAPTURE(FromBinaryShape, containers / ubjson_optimized, make_containers, binary_format::ubjson_optimized);
BENCHMARK_CAPTURE(FromBinaryShape, containers / bjdata, make_containers, binary_format::bjdata);
BENCHMARK_CAPTURE(FromBinaryShape, containers / bson, make_containers, binary_format::bson);
// BSON names every array element, so a large array measures key generation
// rather than scalar decoding and is left out here
BENCHMARK_CAPTURE(FromBinaryShape, scalars / cbor, make_scalars, binary_format::cbor);
BENCHMARK_CAPTURE(FromBinaryShape, scalars / msgpack, make_scalars, binary_format::msgpack);
BENCHMARK_CAPTURE(FromBinaryShape, scalars / ubjson, make_scalars, binary_format::ubjson);
BENCHMARK_CAPTURE(FromBinaryShape, scalars / bjdata, make_scalars, binary_format::bjdata);
/*!
@brief parse an indefinite-length CBOR string
The writer never emits this form, so the input is assembled by hand: 0x7F
opens the string, each chunk is a one-character string, and 0xFF closes it.
*/
static void FromCborChunkedString(benchmark::State& state, const std::size_t chunks)
{
std::vector<std::uint8_t> bytes;
bytes.reserve(2 * chunks + 2);
bytes.push_back(0x7F);
for (std::size_t i = 0; i < chunks; ++i)
{
bytes.push_back(0x61); // string of length 1
bytes.push_back(0x61); // 'a'
}
bytes.push_back(0xFF);
for (auto _ : state)
{
json j = json::from_cbor(bytes);
benchmark::DoNotOptimize(j);
}
state.SetBytesProcessed(state.iterations() * bytes.size());
}
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
BENCHMARK_MAIN(); BENCHMARK_MAIN();
-9
View File
@@ -15,15 +15,6 @@
namespace utils namespace utils
{ {
// Some tests intentionally discard the [[nodiscard]]/JSON_HEDLEY_WARN_UNUSED_RESULT
// return value of a call they only make to exercise its side effects (e.g. checking
// that it does not throw). A plain (void) cast on the call expression does not
// suppress GCC's warning for functions using the GNU __attribute__((warn_unused_result))
// form (as opposed to the C++17 [[nodiscard]] attribute) -- passing the value into an
// ordinary function call does.
template<typename T>
inline void ignore_return_value(T&& /*unused*/) noexcept {}
inline std::vector<std::uint8_t> read_binary_file(const std::string& filename) inline std::vector<std::uint8_t> read_binary_file(const std::string& filename)
{ {
std::ifstream file(filename, std::ios::binary); std::ifstream file(filename, std::ios::binary);
-139
View File
@@ -38,54 +38,6 @@ class huge_binary_t : public std::vector<std::uint8_t>
using huge_binary_json = nlohmann::basic_json < using huge_binary_json = nlohmann::basic_json <
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
double, std::allocator, nlohmann::adl_serializer, huge_binary_t, void >; double, std::allocator, nlohmann::adl_serializer, huge_binary_t, void >;
// a string type that can be made to report a size beyond INT32_MAX without
// allocating that much memory, so BSON length overflow can be tested for
// strings and (embedded) documents as well, following the same idea as
// huge_binary_t.
//
// Unlike huge_binary_t (which is only ever used as the BSON *value* type),
// this type doubles as basic_json's StringType and is therefore also used
// for *object keys* (e.g. "s" or "nested" below). Only the designated test
// value is meant to lie about its size - if every huge_string_t (including
// keys) reported a huge size, the running totals computed while walking the
// BSON document (see calc_bson_object_size & friends in binary_writer.hpp)
// would need more than 32 bits, and on platforms where std::size_t is only
// 32 bits wide that arithmetic would silently wrap around, producing wrong
// (or even unguarded) lengths. The fake size is therefore opt-in via
// as_huge(), and plain strings - in particular object keys - keep reporting
// their real, small size.
class huge_string_t : public std::string
{
public:
using std::string::string;
huge_string_t(const std::string& s) : std::string(s) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
// returns a copy of @a s whose size() pretends to be huge
static huge_string_t as_huge(const std::string& s)
{
huge_string_t result(s);
result.pretend_huge = true;
return result;
}
size_type size() const noexcept
{
if (pretend_huge)
{
// one byte more than the BSON length field can represent
return static_cast<size_type>((std::numeric_limits<std::int32_t>::max)()) + 1;
}
return std::string::size();
}
private:
bool pretend_huge = false;
};
using huge_string_json = nlohmann::basic_json <
std::map, std::vector, huge_string_t, bool, std::int64_t, std::uint64_t,
double, std::allocator, nlohmann::adl_serializer, std::vector<std::uint8_t>, void >;
} // namespace } // namespace
TEST_CASE("BSON") TEST_CASE("BSON")
@@ -152,11 +104,6 @@ TEST_CASE("BSON")
} }
SECTION("lengths exceeding INT32_MAX cannot be serialized to BSON") SECTION("lengths exceeding INT32_MAX cannot be serialized to BSON")
{
// out_of_range.412 is thrown from a single shared helper
// (to_bson_length) that guards the BSON length fields of binary
// values, strings, and (embedded) documents alike
SECTION("binary")
{ {
huge_binary_json j; huge_binary_json j;
j["b"] = huge_binary_json::binary(huge_binary_t{}); j["b"] = huge_binary_json::binary(huge_binary_t{});
@@ -164,27 +111,6 @@ TEST_CASE("BSON")
CHECK_THROWS_WITH_AS(huge_binary_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483661 exceeds maximum of 2147483647", huge_binary_json::out_of_range&); CHECK_THROWS_WITH_AS(huge_binary_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483661 exceeds maximum of 2147483647", huge_binary_json::out_of_range&);
} }
SECTION("string")
{
huge_string_json j;
j["s"] = huge_string_t::as_huge("value");
CHECK_THROWS_WITH_AS(huge_string_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483661 exceeds maximum of 2147483647", huge_string_json::out_of_range&);
}
SECTION("document")
{
// an oversized string nested one level deep makes the
// *embedded* document's own length exceed INT32_MAX as well
huge_string_json nested;
nested["s"] = huge_string_t::as_huge("value");
huge_string_json j;
j["nested"] = nested;
CHECK_THROWS_WITH_AS(huge_string_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483674 exceeds maximum of 2147483647", huge_string_json::out_of_range&);
}
}
SECTION("string length must be at least 1") SECTION("string length must be at least 1")
{ {
// from https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=11175 // from https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=11175
@@ -267,23 +193,6 @@ TEST_CASE("BSON")
CHECK(json::from_bson(result, true, false) == j); CHECK(json::from_bson(result, true, false) == j);
} }
SECTION("non-empty object with bool from a non-0/1 byte (lenient parsing)")
{
// documented lenient behavior (see gh-5333): any non-zero byte
// is accepted as `true`, not just 0x01
std::vector<std::uint8_t> const input =
{
0x0D, 0x00, 0x00, 0x00, // size (little endian)
0x08, // entry: boolean
'e', 'n', 't', 'r', 'y', '\x00',
0x02, // value = 0x02 (neither 0x00 nor 0x01)
0x00 // end marker
};
const json expected = { { "entry", true } };
CHECK(json::from_bson(input) == expected);
}
SECTION("non-empty object with double") SECTION("non-empty object with double")
{ {
json const j = json const j =
@@ -590,29 +499,6 @@ TEST_CASE("BSON")
CHECK(json::from_bson(result, true, false) == j); CHECK(json::from_bson(result, true, false) == j);
} }
SECTION("array elements with non-conforming keys (lenient parsing)")
{
// documented lenient behavior (see gh-5333): BSON array element
// keys are not checked against the required decimal sequence
// "0", "1", "2", ... - elements are taken in encoded order
std::vector<std::uint8_t> const input =
{
0x26, 0x00, 0x00, 0x00, // size (little endian)
0x04, 'e', 'n', 't', 'r', 'y', '\x00', // entry: embedded array
0x1A, 0x00, 0x00, 0x00, // size (little endian)
0x10, '5', 0x00, 0x0A, 0x00, 0x00, 0x00, // key "5" (bogus) -> 10
0x10, 'x', 0x00, 0x14, 0x00, 0x00, 0x00, // key "x" (non-numeric) -> 20
0x10, '1', 0x00, 0x1E, 0x00, 0x00, 0x00, // key "1" (out of order) -> 30
0x00, // end marker (embedded array)
0x00 // end marker
};
const json expected = { { "entry", json::array({10, 20, 30}) } };
CHECK(json::from_bson(input) == expected);
}
SECTION("non-empty object with binary member") SECTION("non-empty object with binary member")
{ {
const size_t N = 10; const size_t N = 10;
@@ -708,31 +594,6 @@ TEST_CASE("BSON")
CHECK(json::from_bson(result, true, false) == j); CHECK(json::from_bson(result, true, false) == j);
} }
SECTION("binary member with subtype 0x02 (old binary) keeps its inner length prefix (lenient parsing)")
{
// documented lenient behavior (see gh-5333): the payload for
// binary subtype 0x02 ("old binary") is returned as-is,
// including its own inner 4-byte length prefix; it is not
// stripped or reinterpreted
std::vector<std::uint8_t> const input =
{
0x17, 0x00, 0x00, 0x00, // size (little endian)
0x05, 'e', 'n', 't', 'r', 'y', '\x00', // entry: binary
0x06, 0x00, 0x00, 0x00, // size of binary (little endian)
0x02, // "old binary" subtype
0x02, 0x00, 0x00, 0x00, // inner length prefix (part of the old-binary payload)
0x68, 0x69, // payload ('h', 'i')
0x00 // end marker
};
// the inner length prefix is part of the (unmodified) payload
const std::vector<std::uint8_t> expected_payload = {0x02, 0x00, 0x00, 0x00, 0x68, 0x69};
const json expected = { { "entry", json::binary(expected_payload, 0x02) } };
CHECK(json::from_bson(input) == expected);
}
SECTION("Some more complex document") SECTION("Some more complex document")
{ {
json const j = json const j =
+1 -161
View File
@@ -23,8 +23,6 @@ using nlohmann::json;
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "test_utils.hpp"
namespace namespace
{ {
class SaxEventLogger class SaxEventLogger
@@ -626,8 +624,7 @@ TEST_CASE("parser class")
SECTION("overflow") SECTION("overflow")
{ {
// overflows during parsing yield an exception // overflows during parsing yield an exception
// empty() is nodiscard; the exception is thrown by parser_helper() itself, before empty() would run CHECK_THROWS_WITH_AS(parser_helper("1.18973e+4932").empty(), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
CHECK_THROWS_WITH_AS(utils::ignore_return_value(parser_helper("1.18973e+4932").empty()), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
} }
SECTION("invalid numbers") SECTION("invalid numbers")
@@ -933,98 +930,6 @@ TEST_CASE("parser class")
CHECK(accept_helper("+1") == false); CHECK(accept_helper("+1") == false);
CHECK(accept_helper("+0") == false); CHECK(accept_helper("+0") == false);
} }
SECTION("issue #5411 - skip conversion when accept() does not need the numeric value")
{
// lexer::scan_number() may skip strtoull()/strtoll() for
// value_unsigned/value_integer tokens when the caller (e.g.
// json::accept()) does not need the converted value, as long
// as the digit count alone guarantees no 64-bit overflow (see
// the "safe_digit_count" fast path in scan_number()). This
// differential test checks that json::accept() (which enables
// the fast path) and json::parse() (which never does) always
// agree, over a corpus that exercises both the fast path
// (<=18 digits) and the untouched, exact fallback path (>=19
// digits) -- including reclassification of huge digit-only
// integers to a (possibly non-finite) floating-point value.
const std::vector<std::pair<std::string, bool>> cases =
{
// normal small/large integers, both signs
{"0", true}, {"1", true}, {"-1", true}, {"42", true}, {"-42", true},
{"123456789", true}, {"-123456789", true},
// digit-count boundary around the 18-digit safe cutoff (both signs)
{std::string(17, '9'), true},
{std::string(18, '9'), true},
{std::string(19, '9'), true},
{std::string(20, '9'), true},
{"-" + std::string(17, '9'), true},
{"-" + std::string(18, '9'), true},
{"-" + std::string(19, '9'), true},
{"-" + std::string(20, '9'), true},
// 64-bit boundaries
{"9223372036854775807", true}, // INT64_MAX
{"-9223372036854775808", true}, // INT64_MIN
{"18446744073709551615", true}, // UINT64_MAX
{"18446744073709551616", true}, // UINT64_MAX + 1 (overflows uint64_t, finite double)
// the 28-digit example from the issue: overflows uint64_t
// but is finite as a double, so the scanner reclassifies
// it to value_float and it is accepted
{"9999999999999999999999999999", true},
// huge digit-only integers that overflow even a double -> rejected
{std::string(309, '9'), false},
{std::string(400, '9'), false},
{"1" + std::string(400, '0'), false},
// 1e999 / 1e400 style overflow -> rejected
{"1e999", false},
{"1e400", false},
{"-1e999", false},
{"1E999", false},
// values straddling DBL_MAX
{"1.7976931348623157e308", true}, // <= DBL_MAX, finite
{"1.7976931348623159e308", false}, // > DBL_MAX, overflows to inf
// a mix of other valid/invalid numeric syntax
{"3.14159", true},
{"-0.0", true},
{"1.0e10", true},
{"01", false},
{"-", false},
{"1.", false},
{"1e", false},
{"+1", false},
};
for (const auto& c : cases)
{
const std::string& number = c.first;
const bool expected = c.second;
CAPTURE(number)
CAPTURE(expected)
// accept() takes the fast path (skips conversion when possible)
CHECK(json::accept(number) == expected);
// parse() always performs the full conversion; it must agree
json j;
CHECK_NOTHROW(json::parser(nlohmann::detail::input_adapter(number), nullptr, false).parse(true, j));
CHECK(!j.is_discarded() == expected);
// wrap in an array so get_token() is exercised beyond the
// very first (constructor-time) scan as well
std::string wrapped = "[";
wrapped += number;
wrapped += ",";
wrapped += number;
wrapped += "]";
CHECK(json::accept(wrapped) == expected);
}
}
} }
} }
@@ -1489,71 +1394,6 @@ TEST_CASE("parser class")
CHECK(accept_helper("\"\\uD80C\\uFFFF\"") == false); CHECK(accept_helper("\"\\uD80C\\uFFFF\"") == false);
} }
#if !defined(JSON_NOEXCEPTION)
SECTION("issue #5412 - whitespace skipping bookkeeping (compact vs. pretty-printed)")
{
// lexer::skip_whitespace() reads its first character with get() (to
// honor a possibly pending unget() from the previous token) and every
// further whitespace character with get_ignoring_pending_unget() (a
// get() variant that skips the then-always-false next_unget check).
// This must not change the reported byte offset, line, or column of
// a syntax error, even when a long run of whitespace containing
// multiple newlines is skipped beforehand (as with pretty-printed
// input). The expected values below were captured from the
// unmodified do-while(get()) loop, so any regression that miscounts
// characters or newlines while skipping whitespace changes them.
const auto check_error = [](const std::string & input, std::size_t expected_byte,
const std::string & expected_what)
{
CAPTURE(input)
try
{
json _ = json::parse(input);
FAIL_CHECK("expected a parse_error, but parsing succeeded");
}
catch (const json::parse_error& e)
{
CHECK(e.byte == expected_byte);
CHECK(std::string(e.what()) == expected_what);
}
};
// a nested document, serialized both compactly and pretty-printed
// (dump(4)), each truncated right before the final closing '}' so
// that the parser hits EOF after skipping all of the (in the
// pretty-printed case, substantial) indentation whitespace
const json doc =
{
{"a", 1},
{"b", json::array({true, false, nullptr, "x"})},
{"c", json::object({{"d", 3.14}, {"e", json::array({1, 2, 3})}})}
};
const std::string compact = doc.dump();
const std::string pretty = doc.dump(4);
check_error(compact.substr(0, compact.size() - 1), 60,
"[json.exception.parse_error.101] parse error at line 1, column 60: syntax error while parsing object - unexpected end of input; expected '}'");
check_error(pretty.substr(0, pretty.size() - 1), 193,
"[json.exception.parse_error.101] parse error at line 17, column 1: syntax error while parsing object - unexpected end of input; expected '}'");
// an invalid token appearing after several indented, multi-line
// whitespace runs vs. the same document without any of that
// whitespace
check_error(R"({
"a": 1,
"b": [
true,
false
],
"c": @
})", 70,
"[json.exception.parse_error.101] parse error at line 7, column 10: syntax error while parsing value - invalid literal; last read: '\"c\": @'");
check_error(R"({"a":1,"b":[true,false],"c":@})", 29,
"[json.exception.parse_error.101] parse error at line 1, column 29: syntax error while parsing value - invalid literal; last read: '\"c\":@'");
}
#endif
SECTION("tests found by mutate++") SECTION("tests found by mutate++")
{ {
// test case to make sure no comma precedes the first key // test case to make sure no comma precedes the first key
@@ -22,8 +22,6 @@ using nlohmann::json;
#include <valarray> #include <valarray>
#include "test_utils.hpp"
namespace namespace
{ {
class SaxEventLogger class SaxEventLogger
@@ -631,8 +629,7 @@ TEST_CASE("parser class")
SECTION("overflow") SECTION("overflow")
{ {
// overflows during parsing yield an exception // overflows during parsing yield an exception
// empty() is nodiscard; the exception is thrown by parser_helper() itself, before empty() would run CHECK_THROWS_WITH_AS(parser_helper("1.18973e+4932").empty(), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
CHECK_THROWS_WITH_AS(utils::ignore_return_value(parser_helper("1.18973e+4932").empty()), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
} }
SECTION("invalid numbers") SECTION("invalid numbers")
-31
View File
@@ -1389,37 +1389,6 @@ TEST_CASE("value conversion")
// CHECK(m5["one"] == "eins"); // CHECK(m5["one"] == "eins");
} }
SECTION("reserve is called on containers that support it (#5406)")
{
// build a larger object so that a missing/incorrect reserve()
// call would be more likely to corrupt or drop elements
json j_large;
for (int i = 0; i < 100; ++i)
{
j_large[std::to_string(i)] = i;
}
SECTION("std::unordered_map (supports reserve)")
{
const auto m = j_large.get<std::unordered_map<std::string, int>>();
CHECK(m.size() == 100);
for (int i = 0; i < 100; ++i)
{
CHECK(m.at(std::to_string(i)) == i);
}
}
SECTION("std::map (no reserve, fallback path)")
{
const auto m = j_large.get<std::map<std::string, int>>();
CHECK(m.size() == 100);
for (int i = 0; i < 100; ++i)
{
CHECK(m.at(std::to_string(i)) == i);
}
}
}
SECTION("std::multimap") SECTION("std::multimap")
{ {
j1.get<std::multimap<std::string, int>>(); j1.get<std::multimap<std::string, int>>();
-31
View File
@@ -273,36 +273,5 @@ TEST_CASE("Regression tests for extended diagnostics")
CHECK(j1["numbers"]["two"] == 2); CHECK(j1["numbers"]["two"] == 2);
CHECK(j1["string"] == "t"); CHECK(j1["string"] == "t");
} }
SECTION("Regression test - swap(array_t&)/swap(object_t&) must update JSON_DIAGNOSTICS parent pointers")
{
// swap(array_t&)
{
json j = json::array();
json::array_t arr = {json::array({1})};
j.swap(arr);
// parent pointers of the moved-in elements must point into j, not
// into the now-defunct free-standing array_t
CHECK_THROWS_WITH_AS(j[0][0].get<std::string>(), "[json.exception.type_error.302] (/0/0) type must be string, but is number", json::type_error);
// must not trigger assert_invariant() in a debug/assert-enabled build
json const k = j;
CHECK(k == j);
}
// swap(object_t&)
{
json o = json::object();
json::object_t obj = {{"a", json::array({1})}};
o.swap(obj);
CHECK_THROWS_WITH_AS(o["a"][0].get<std::string>(), "[json.exception.type_error.302] (/a/0) type must be string, but is number", json::type_error);
// must not trigger assert_invariant() in a debug/assert-enabled build
json const p = o;
CHECK(p == o);
}
}
} }
-42
View File
@@ -1,42 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// This file contains the C++17-only part of unit-items.cpp (structured
// bindings support for json::items()). It is kept in a separate
// translation unit so the (much larger) unit-items.cpp does not need to
// be compiled a second time just for this one SECTION.
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
using nlohmann::json;
#ifdef JSON_HAS_CPP_17
#include <map>
#include <string>
TEST_CASE("items()")
{
SECTION("object")
{
SECTION("structured bindings")
{
json j = { {"A", 1}, {"B", 2} };
std::map<std::string, int> m;
for (auto const&[key, value] : j.items())
{
m.emplace(key, value);
}
CHECK(j.get<decltype(m)>() == m);
}
}
}
#endif
+16
View File
@@ -862,6 +862,22 @@ TEST_CASE("items()")
CHECK(counter == 3); CHECK(counter == 3);
} }
#ifdef JSON_HAS_CPP_17
SECTION("structured bindings")
{
json j = { {"A", 1}, {"B", 2} };
std::map<std::string, int> m;
for (auto const&[key, value] : j.items())
{
m.emplace(key, value);
}
CHECK(j.get<decltype(m)>() == m);
}
#endif
} }
SECTION("const object") SECTION("const object")
-186
View File
@@ -1389,192 +1389,6 @@ TEST_CASE("JSON patch - add to a primitive parent (regression #4292)")
} }
} }
TEST_CASE("JSON patch - remove with primitive or null parent (regression #5396)")
{
// Regression test for https://github.com/nlohmann/json/issues/5396
//
// RFC 6902 (§4.2) requires the target location of a "remove" operation
// to exist. When the target's parent resolves to a primitive value or
// null, the operation must fail. Previously operation_remove silently
// did nothing in this case (neither the "is_object" nor the "is_array"
// branch matched, and there was no final "else"), so the patch appeared
// to succeed without changing the document. It now throws
// out_of_range.413.
SECTION("parent is a primitive (number)")
{
json const doc = {{"a", 1}};
json const patch = {{{"op", "remove"}, {"path", "/a/b"}}};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.413] (/a) cannot remove value: the JSON Patch 'remove' target's parent is of type number, but must be an object or array", json::out_of_range&);
#else
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.413] cannot remove value: the JSON Patch 'remove' target's parent is of type number, but must be an object or array", json::out_of_range&);
#endif
}
SECTION("parent is a primitive (string)")
{
json const doc = {{"foo", {{"bar", "a string"}}}};
json const patch = {{{"op", "remove"}, {"path", "/foo/bar/baz"}}};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.413] (/foo/bar) cannot remove value: the JSON Patch 'remove' target's parent is of type string, but must be an object or array", json::out_of_range&);
#else
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.413] cannot remove value: the JSON Patch 'remove' target's parent is of type string, but must be an object or array", json::out_of_range&);
#endif
}
SECTION("top-level document is null")
{
json const doc = nullptr;
json const patch = {{{"op", "remove"}, {"path", "/a"}}};
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.413] cannot remove value: the JSON Patch 'remove' target's parent is of type null, but must be an object or array", json::out_of_range&);
}
SECTION("legitimate removes still work")
{
// object member
json const doc1 = {{"a", 1}, {"b", 2}};
json const patch1 = {{{"op", "remove"}, {"path", "/a"}}};
CHECK(doc1.patch(patch1) == json({{"b", 2}}));
// array element
json const doc2 = R"([1, 2, 3])"_json;
json const patch2 = {{{"op", "remove"}, {"path", "/1"}}};
CHECK(doc2.patch(patch2) == R"([1, 3])"_json);
}
}
TEST_CASE("JSON patch - move where 'from' is a proper prefix of 'path' (regression #5397)")
{
// Regression test for https://github.com/nlohmann/json/issues/5397
//
// RFC 6902 (§4.4) forbids "from" from being a proper prefix of "path"
// for a "move" operation: "a location cannot be moved into one of its
// children." "move" is implemented as remove-then-add; for an object
// target this happened to throw anyway as a side effect of the "add"
// step re-resolving through the now-removed parent, but for an array
// target the removal shifted subsequent indices, so "path" silently
// re-resolved to a different element and the operation "succeeded"
// with a corrupted result. It now throws out_of_range.414 for both
// object and array targets.
SECTION("array target (from the issue)")
{
json const doc = R"([[1,2],[3]])"_json;
json const patch = {{{"op", "move"}, {"from", "/0"}, {"path", "/0/0"}}};
#if JSON_DIAGNOSTIC_POSITIONS
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] (bytes 0-11) cannot move value: 'from' path '/0' is a proper prefix of 'path' '/0/0'", json::out_of_range&);
#else
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] cannot move value: 'from' path '/0' is a proper prefix of 'path' '/0/0'", json::out_of_range&);
#endif
}
SECTION("object target")
{
json const doc = R"({"a": {"b": 1}})"_json;
json const patch = {{{"op", "move"}, {"from", "/a"}, {"path", "/a/b"}}};
#if JSON_DIAGNOSTIC_POSITIONS
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] (bytes 0-15) cannot move value: 'from' path '/a' is a proper prefix of 'path' '/a/b'", json::out_of_range&);
#else
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] cannot move value: 'from' path '/a' is a proper prefix of 'path' '/a/b'", json::out_of_range&);
#endif
}
SECTION("from == path is not a proper prefix and must not be rejected")
{
// "from" equal to "path" is a no-op move; it is not a *proper*
// prefix relationship, so this new check must not reject it.
json const doc = R"({"a": 1, "b": 2})"_json;
json const patch = {{{"op", "move"}, {"from", "/a"}, {"path", "/a"}}};
CHECK(doc.patch(patch) == doc);
}
SECTION("raw string prefix that is not a pointer-token prefix must be allowed")
{
// "/ab" is a string-prefix of "/abc/x" as raw text, but "ab" and
// "abc" are different reference tokens, so this is NOT a
// pointer-token prefix relationship and the move must succeed.
// This is the key case proving the check compares tokens, not
// raw pointer text (a naive std::string prefix/rfind check on
// the undecoded pointer would wrongly reject this).
json const doc = R"({"ab": 1, "abc": {"x": 2}})"_json;
json const patch = {{{"op", "move"}, {"from", "/ab"}, {"path", "/abc/x"}}};
json const result = R"({"abc": {"x": 1}})"_json;
CHECK(doc.patch(patch) == result);
}
SECTION("escaped reference tokens are compared unescaped")
{
// "from" is the single token "a/b" (escaped as "a~1b"); "path"
// addresses member "x" of that same value, so "from" is a
// proper (token-level) prefix of "path" and must be rejected.
json const doc = R"({"a/b": {"x": 1}})"_json;
json const patch = {{{"op", "move"}, {"from", "/a~1b"}, {"path", "/a~1b/x"}}};
#if JSON_DIAGNOSTIC_POSITIONS
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] (bytes 0-17) cannot move value: 'from' path '/a~1b' is a proper prefix of 'path' '/a~1b/x'", json::out_of_range&);
#else
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] cannot move value: 'from' path '/a~1b' is a proper prefix of 'path' '/a~1b/x'", json::out_of_range&);
#endif
}
SECTION("ordinary valid moves still work")
{
// unrelated top-level members
json const doc1 = R"({"a": 1, "b": 2})"_json;
json const patch1 = {{{"op", "move"}, {"from", "/a"}, {"path", "/c"}}};
CHECK(doc1.patch(patch1) == R"({"b": 2, "c": 1})"_json);
// sibling paths that share a textual prefix but are unrelated
json const doc2 = R"({"a": {"x": 1}, "b": {"y": 2}})"_json;
json const patch2 = {{{"op", "move"}, {"from", "/a/x"}, {"path", "/b/z"}}};
CHECK(doc2.patch(patch2) == R"({"a": {}, "b": {"y": 2, "z": 1}})"_json);
// "path" is a proper prefix of "from" (the reverse relationship,
// which RFC 6902 does not forbid)
json const doc3 = R"({"a": {"b": 1}})"_json;
json const patch3 = {{{"op", "move"}, {"from", "/a/b"}, {"path", "/a"}}};
CHECK(doc3.patch(patch3) == R"({"a": 1})"_json);
}
SECTION("root 'from' is a proper prefix of every non-root 'path'")
{
// the whole document is a proper prefix of any location inside it
json const doc = R"({"a": 1})"_json;
json const patch = {{{"op", "move"}, {"from", ""}, {"path", "/a"}}};
#if JSON_DIAGNOSTIC_POSITIONS
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] (bytes 0-8) cannot move value: 'from' path '' is a proper prefix of 'path' '/a'", json::out_of_range&);
#else
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] cannot move value: 'from' path '' is a proper prefix of 'path' '/a'", json::out_of_range&);
#endif
}
SECTION("root 'path' is never a proper prefix violation for a non-root 'from'")
{
// the reverse of the above: moving a non-root location to the root
// is the "path is a prefix of from" relationship, which RFC 6902
// permits (already covered generally above; this pins the root
// case specifically, since root is the one path with no reference
// tokens at all)
json const doc = R"({"a": {"b": 1}})"_json;
json const patch = {{{"op", "move"}, {"from", "/a"}, {"path", ""}}};
CHECK(doc.patch(patch) == R"({"b": 1})"_json);
}
SECTION("the array-append token '-' is an ordinary child token")
{
// "-" (append-to-array) addresses a location *inside* the array,
// so "from" pointing at the array is still a proper prefix of
// "path" ending in "-" and must be rejected like any other child.
json const doc = R"({"a": [1, 2]})"_json;
json const patch = {{{"op", "move"}, {"from", "/a"}, {"path", "/a/-"}}};
#if JSON_DIAGNOSTIC_POSITIONS
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] (bytes 0-13) cannot move value: 'from' path '/a' is a proper prefix of 'path' '/a/-'", json::out_of_range&);
#else
CHECK_THROWS_WITH_AS(doc.patch(patch), "[json.exception.out_of_range.414] cannot move value: 'from' path '/a' is a proper prefix of 'path' '/a/-'", json::out_of_range&);
#endif
}
}
TEST_CASE("JSON patch - diff emits array removals in descending index order") TEST_CASE("JSON patch - diff emits array removals in descending index order")
{ {
SECTION("array shrunk to empty") SECTION("array shrunk to empty")
-42
View File
@@ -319,44 +319,6 @@ TEST_CASE("JSON pointers")
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&); CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&); CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
// #5395: contains() must not throw for a reference token that is a
// syntactically valid array index but numerically exceeds ULLONG_MAX
// (causing strtoull() to set errno to ERANGE) -- it should just report
// that the pointer does not resolve to an element
CHECK(!j.contains(jp));
CHECK(!j_const.contains(jp));
}
{
// #5395: same as above, but using the exact reproduction from the issue
json::json_pointer const jp("/99999999999999999999");
std::string const throw_msg = "[json.exception.out_of_range.404] unresolved reference token '99999999999999999999'";
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(j.at(jp) = 1, throw_msg.c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(j_const.at(jp) == 1, throw_msg.c_str(), json::out_of_range&);
CHECK(!j.contains(jp));
CHECK(!j_const.contains(jp));
}
{
// #5395: a reference token that is numerically representable in
// unsigned long long but exceeds size_type's max (e.g. ULLONG_MAX
// itself on typical 64-bit platforms, where size_type's max equals
// ULLONG_MAX) must not make contains() throw either
json::json_pointer const jp("/18446744073709551615");
std::string const throw_msg = "[json.exception.out_of_range.410] array index 18446744073709551615 exceeds size_type";
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(j.at(jp) = 1, throw_msg.c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(j_const.at(jp) == 1, throw_msg.c_str(), json::out_of_range&);
CHECK(!j.contains(jp));
CHECK(!j_const.contains(jp));
} }
// on some machines, the check below is not constant // on some machines, the check below is not constant
@@ -372,10 +334,6 @@ TEST_CASE("JSON pointers")
CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&); CHECK_THROWS_WITH_AS(j[jp] = 1, throw_msg.c_str(), json::out_of_range&);
CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&); CHECK_THROWS_WITH_AS(j_const[jp] == 1, throw_msg.c_str(), json::out_of_range&);
// #5395: contains() must not throw for a reference token exceeding size_type's max
CHECK(!j.contains(jp));
CHECK(!j_const.contains(jp));
} }
DOCTEST_MSVC_SUPPRESS_WARNING_POP DOCTEST_MSVC_SUPPRESS_WARNING_POP
-34
View File
@@ -1,34 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// This file makes sure that none of the internal JSON_HEDLEY_* macros (vendored
// from https://nemequ.github.io/hedley/, see
// include/nlohmann/thirdparty/hedley/hedley.hpp) leak into the including
// translation unit. include/nlohmann/detail/macro_unscope.hpp is supposed to
// #undef every JSON_HEDLEY_* macro (via hedley_undef.hpp) once json.hpp has
// been fully processed. See https://github.com/nlohmann/json/issues/5408,
// where JSON_HEDLEY_PRAGMA, JSON_HEDLEY_PREDICT_TRUE, JSON_HEDLEY_PREDICT_FALSE,
// and JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE escaped this cleanup because
// hedley_undef.hpp had no matching #undef for them.
//
// hedley_undef_checks.inc (included below) is generated at CMake configure/
// build time by cmake/scripts/gen_hedley_undef_check.cmake, which derives the
// full list of JSON_HEDLEY_* macro names directly from hedley.hpp. That way
// this test covers every macro Hedley actually defines -- not a hardcoded
// snapshot that would silently go stale the next time `make update_hedley`
// runs -- and can never drift from the vendored header.
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
TEST_CASE("JSON_HEDLEY macros do not leak after including json.hpp")
{
#include "hedley_undef_checks.inc"
CHECK(true); // keep an assertion when nothing leaked
}
-81
View File
@@ -81,84 +81,3 @@ TEST_CASE("regression test for issue #3732 - iteration_proxy_value<iter_impl<ord
}; };
static_cast<void>(fn); static_cast<void>(fn);
} }
TEST_CASE("regression test - diff() must account for ordered_json member order")
{
SECTION("pure reorder, no value changes")
{
ordered_json a = {{"a", 1}, {"b", 2}};
ordered_json b = {{"b", 2}, {"a", 1}};
CHECK(a != b); // order-sensitive equality
CHECK(a.patch(ordered_json::diff(a, b)) == b);
}
SECTION("new key must land at the front")
{
ordered_json c = {{"b", 2}};
ordered_json e = {{"a", 1}, {"b", 2}};
CHECK(c.patch(ordered_json::diff(c, e)) == e);
}
SECTION("reorder plus a value change on one of the reordered keys")
{
ordered_json a = {{"a", 1}, {"b", 2}};
ordered_json b = {{"b", 20}, {"a", 1}};
CHECK(a != b);
CHECK(a.patch(ordered_json::diff(a, b)) == b);
}
SECTION("reorder plus a deleted key")
{
ordered_json a = {{"a", 1}, {"b", 2}, {"c", 3}};
ordered_json b = {{"b", 2}, {"a", 1}};
CHECK(a != b);
CHECK(a.patch(ordered_json::diff(a, b)) == b);
}
SECTION("reorder plus a nested value that itself needs a recursive diff")
{
ordered_json a = {{"a", {{"x", 1}, {"y", 2}}}, {"b", 2}};
ordered_json b = {{"b", 2}, {"a", {{"x", 1}, {"y", 99}}}};
CHECK(a != b);
CHECK(a.patch(ordered_json::diff(a, b)) == b);
}
SECTION("three or more keys shuffled into a different order")
{
ordered_json a = {{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}};
ordered_json b = {{"d", 4}, {"b", 2}, {"a", 1}, {"c", 3}};
CHECK(a != b);
CHECK(a.patch(ordered_json::diff(a, b)) == b);
}
SECTION("matching order still produces a minimal patch (fast path unaffected)")
{
ordered_json a = {{"a", 1}, {"b", 2}, {"c", 3}};
ordered_json b = {{"a", 1}, {"b", 20}, {"c", 3}};
auto p = ordered_json::diff(a, b);
// only the changed value should be touched, not a wholesale remove+add
CHECK(p.size() == 1);
CHECK(p[0]["op"] == "replace");
CHECK(p[0]["path"] == "/b");
CHECK(a.patch(p) == b);
}
SECTION("plain json (std::map-backed) is unaffected by same-key-different-insertion-order")
{
json a;
a["b"] = 2;
a["a"] = 1;
json b;
b["a"] = 1;
b["b"] = 2;
// std::map iteration is always sorted by key, so a == b regardless of
// insertion order, and diff() must still produce the same minimal
// (empty) result as before this fix
CHECK(a == b);
auto p = json::diff(a, b);
CHECK(p.empty());
CHECK(a.patch(p) == b);
}
}
+5 -3
View File
@@ -29,7 +29,10 @@ using nlohmann::json;
#include <limits> #include <limits>
#include <cstdio> #include <cstdio>
#include "make_test_data_available.hpp" #include "make_test_data_available.hpp"
#include "test_utils.hpp"
#ifdef JSON_HAS_CPP_17
#include <variant>
#endif
#include "fifo_map.hpp" #include "fifo_map.hpp"
@@ -1370,8 +1373,7 @@ TEST_CASE("regression tests 1")
std::array<uint8_t, 28> key1 = {{ 103, 92, 117, 48, 48, 48, 55, 92, 114, 215, 126, 214, 95, 92, 34, 174, 40, 71, 38, 174, 40, 71, 38, 223, 134, 247, 127, 0 }}; std::array<uint8_t, 28> key1 = {{ 103, 92, 117, 48, 48, 48, 55, 92, 114, 215, 126, 214, 95, 92, 34, 174, 40, 71, 38, 174, 40, 71, 38, 223, 134, 247, 127, 0 }};
std::string const key1_str(reinterpret_cast<char*>(key1.data())); std::string const key1_str(reinterpret_cast<char*>(key1.data()));
json const j = key1_str; json const j = key1_str;
// dump() is nodiscard; the exception is thrown by dump() itself before it would return CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 10: 0x7E", json::type_error&);
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 10: 0x7E", json::type_error&);
} }
#if JSON_USE_IMPLICIT_CONVERSIONS #if JSON_USE_IMPLICIT_CONVERSIONS
+2 -7
View File
@@ -31,8 +31,6 @@ using ordered_json = nlohmann::ordered_json;
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "test_utils.hpp"
#ifdef JSON_HAS_CPP_17 #ifdef JSON_HAS_CPP_17
#include <any> #include <any>
#include <variant> #include <variant>
@@ -641,8 +639,7 @@ TEST_CASE("regression tests 2")
s += static_cast<char>(i); s += static_cast<char>(i);
} }
dump_test["1"] = s; dump_test["1"] = s;
// dump() is nodiscard; this only checks that dumping does not throw/crash dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace);
utils::ignore_return_value(dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace));
} }
} }
@@ -734,14 +731,12 @@ TEST_CASE("regression tests 2")
{ {
const std::array<unsigned char, 23> data = {{0x81, 0xA4, 0x64, 0x61, 0x74, 0x61, 0xC4, 0x0F, 0x33, 0x30, 0x30, 0x32, 0x33, 0x34, 0x30, 0x31, 0x30, 0x37, 0x30, 0x35, 0x30, 0x31, 0x30}}; const std::array<unsigned char, 23> data = {{0x81, 0xA4, 0x64, 0x61, 0x74, 0x61, 0xC4, 0x0F, 0x33, 0x30, 0x30, 0x32, 0x33, 0x34, 0x30, 0x31, 0x30, 0x37, 0x30, 0x35, 0x30, 0x31, 0x30}};
const json j = json::from_msgpack(data.data(), data.size()); const json j = json::from_msgpack(data.data(), data.size());
// dump() is nodiscard; this only checks that dumping does not throw
CHECK_NOTHROW( CHECK_NOTHROW(
utils::ignore_return_value(
j.dump(4, // Indent j.dump(4, // Indent
' ', // Indent char ' ', // Indent char
false, // Ensure ascii false, // Ensure ascii
json::error_handler_t::strict // Error json::error_handler_t::strict // Error
))); ));
} }
SECTION("PR #2181 - regression bug with lvalue") SECTION("PR #2181 - regression bug with lvalue")
+6 -11
View File
@@ -15,8 +15,6 @@ using nlohmann::json;
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
#include "test_utils.hpp"
TEST_CASE("serialization") TEST_CASE("serialization")
{ {
SECTION("operator<<") SECTION("operator<<")
@@ -86,9 +84,8 @@ TEST_CASE("serialization")
{ {
const json j = "ä\xA9ü"; const json j = "ä\xA9ü";
// dump() is nodiscard; the exception is thrown by dump() itself before it would return CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&);
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&); CHECK_THROWS_WITH_AS(j.dump(1, ' ', false, json::error_handler_t::strict), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&);
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump(1, ' ', false, json::error_handler_t::strict)), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&);
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"äü\""); CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"äü\"");
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"ä\xEF\xBF\xBDü\""); CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"ä\xEF\xBF\xBDü\"");
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"\\u00e4\\ufffd\\u00fc\""); CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"\\u00e4\\ufffd\\u00fc\"");
@@ -98,9 +95,8 @@ TEST_CASE("serialization")
{ {
const json j = "123\xC2"; const json j = "123\xC2";
// dump() is nodiscard; the exception is thrown by dump() itself before it would return CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0xC2", json::type_error&);
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0xC2", json::type_error&); CHECK_THROWS_AS(j.dump(1, ' ', false, json::error_handler_t::strict), json::type_error&);
CHECK_THROWS_AS(utils::ignore_return_value(j.dump(1, ' ', false, json::error_handler_t::strict)), json::type_error&);
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123\""); CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123\"");
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\""); CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\"");
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd\""); CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd\"");
@@ -110,9 +106,8 @@ TEST_CASE("serialization")
{ {
const json j = "123\xF1\xB0\x34\x35\x36"; const json j = "123\xF1\xB0\x34\x35\x36";
// dump() is nodiscard; the exception is thrown by dump() itself before it would return CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 5: 0x34", json::type_error&);
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 5: 0x34", json::type_error&); CHECK_THROWS_AS(j.dump(1, ' ', false, json::error_handler_t::strict), json::type_error&);
CHECK_THROWS_AS(utils::ignore_return_value(j.dump(1, ' ', false, json::error_handler_t::strict)), json::type_error&);
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123456\""); CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123456\"");
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\x34\x35\x36\""); CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\x34\x35\x36\"");
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd456\""); CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd456\"");
+2 -5
View File
@@ -17,7 +17,6 @@ using nlohmann::json;
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
#include "make_test_data_available.hpp" #include "make_test_data_available.hpp"
#include "test_utils.hpp"
TEST_CASE("Unicode (1/5)" * doctest::skip()) TEST_CASE("Unicode (1/5)" * doctest::skip())
{ {
@@ -241,8 +240,7 @@ void roundtrip(bool success_expected, const std::string& s)
if (success_expected) if (success_expected)
{ {
// serialization succeeds // serialization succeeds
// dump() is nodiscard; this only checks that dumping does not throw CHECK_NOTHROW(j.dump());
CHECK_NOTHROW(utils::ignore_return_value(j.dump()));
// exclude parse test for U+0000 // exclude parse test for U+0000
if (s[0] != '\0') if (s[0] != '\0')
@@ -261,8 +259,7 @@ void roundtrip(bool success_expected, const std::string& s)
else else
{ {
// serialization fails // serialization fails
// dump() is nodiscard; the exception is thrown by dump() itself before it would return CHECK_THROWS_AS(j.dump(), json::type_error&);
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
// parsing JSON text fails // parsing JSON text fails
CHECK_THROWS_AS(_ = json::parse(ps), json::parse_error&); CHECK_THROWS_AS(_ = json::parse(ps), json::parse_error&);
+1 -3
View File
@@ -19,7 +19,6 @@ using nlohmann::json;
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
#include "make_test_data_available.hpp" #include "make_test_data_available.hpp"
#include "test_utils.hpp"
// this test suite uses static variables with non-trivial destructors // this test suite uses static variables with non-trivial destructors
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
@@ -98,8 +97,7 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
else else
{ {
// strict mode must throw if success is not expected // strict mode must throw if success is not expected
// dump() is nodiscard; the exception is thrown by dump() itself before it would return CHECK_THROWS_AS(j.dump(), json::type_error&);
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
// ignore and replace must create different dumps // ignore and replace must create different dumps
CHECK(s_ignored != s_replaced); CHECK(s_ignored != s_replaced);
+1 -3
View File
@@ -19,7 +19,6 @@ using nlohmann::json;
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
#include "make_test_data_available.hpp" #include "make_test_data_available.hpp"
#include "test_utils.hpp"
// this test suite uses static variables with non-trivial destructors // this test suite uses static variables with non-trivial destructors
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
@@ -98,8 +97,7 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
else else
{ {
// strict mode must throw if success is not expected // strict mode must throw if success is not expected
// dump() is nodiscard; the exception is thrown by dump() itself before it would return CHECK_THROWS_AS(j.dump(), json::type_error&);
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
// ignore and replace must create different dumps // ignore and replace must create different dumps
CHECK(s_ignored != s_replaced); CHECK(s_ignored != s_replaced);
+1 -3
View File
@@ -19,7 +19,6 @@ using nlohmann::json;
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
#include "make_test_data_available.hpp" #include "make_test_data_available.hpp"
#include "test_utils.hpp"
// this test suite uses static variables with non-trivial destructors // this test suite uses static variables with non-trivial destructors
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
@@ -98,8 +97,7 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
else else
{ {
// strict mode must throw if success is not expected // strict mode must throw if success is not expected
// dump() is nodiscard; the exception is thrown by dump() itself before it would return CHECK_THROWS_AS(j.dump(), json::type_error&);
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
// ignore and replace must create different dumps // ignore and replace must create different dumps
CHECK(s_ignored != s_replaced); CHECK(s_ignored != s_replaced);
+1 -3
View File
@@ -19,7 +19,6 @@ using nlohmann::json;
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
#include "make_test_data_available.hpp" #include "make_test_data_available.hpp"
#include "test_utils.hpp"
// this test suite uses static variables with non-trivial destructors // this test suite uses static variables with non-trivial destructors
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
@@ -98,8 +97,7 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
else else
{ {
// strict mode must throw if success is not expected // strict mode must throw if success is not expected
// dump() is nodiscard; the exception is thrown by dump() itself before it would return CHECK_THROWS_AS(j.dump(), json::type_error&);
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
// ignore and replace must create different dumps // ignore and replace must create different dumps
CHECK(s_ignored != s_replaced); CHECK(s_ignored != s_replaced);