mirror of
https://github.com/nlohmann/json.git
synced 2026-10-07 06:57:14 +00:00
Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf354aa4a4 | ||
|
|
268ddeb2f0 | ||
|
|
071f79b524 | ||
|
|
23381929ff | ||
|
|
0d28a3f082 | ||
|
|
1a4c9dea73 | ||
|
|
162e13b86f | ||
|
|
69874e4544 | ||
|
|
a7d387a762 | ||
|
|
d41887511b | ||
|
|
9b179cee1e | ||
|
|
6d4543e743 | ||
|
|
5ecb704f6b | ||
|
|
0490778fc3 | ||
|
|
0a365865f9 | ||
|
|
5379e04ce4 |
No files matched your search
@@ -149,13 +149,6 @@ jobs:
|
|||||||
VERSION_MAJOR=$(grep -m1 'define NLOHMANN_JSON_VERSION_MAJOR' $ABI_MACROS | grep -o '[0-9]\+')
|
VERSION_MAJOR=$(grep -m1 'define NLOHMANN_JSON_VERSION_MAJOR' $ABI_MACROS | grep -o '[0-9]\+')
|
||||||
VERSION_MINOR=$(grep -m1 'define NLOHMANN_JSON_VERSION_MINOR' $ABI_MACROS | grep -o '[0-9]\+')
|
VERSION_MINOR=$(grep -m1 'define NLOHMANN_JSON_VERSION_MINOR' $ABI_MACROS | grep -o '[0-9]\+')
|
||||||
VERSION_PATCH=$(grep -m1 'define NLOHMANN_JSON_VERSION_PATCH' $ABI_MACROS | grep -o '[0-9]\+')
|
VERSION_PATCH=$(grep -m1 'define NLOHMANN_JSON_VERSION_PATCH' $ABI_MACROS | grep -o '[0-9]\+')
|
||||||
# The template is what this check verifies the committed natvis
|
|
||||||
# file against, so take it from this PR rather than from the
|
|
||||||
# develop checkout: generate_natvis.py loads the template from its
|
|
||||||
# own directory, and with develop's copy a PR that changes the
|
|
||||||
# template could never pass. Like the macro_builder step above,
|
|
||||||
# this only renders PR content in a job without credentials.
|
|
||||||
cp tools/generate_natvis/nlohmann_json.natvis.j2 $NATVIS_TOOL_DIR/
|
|
||||||
python3 $NATVIS_TOOL_DIR/generate_natvis.py --version "$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH" $MAIN_DIR
|
python3 $NATVIS_TOOL_DIR/generate_natvis.py --version "$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH" $MAIN_DIR
|
||||||
|
|
||||||
- name: Build patch and check for differences
|
- name: Build patch and check for differences
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
macos-14:
|
macos-15:
|
||||||
runs-on: macos-14 # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
|
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
xcode: ['15.0.1', '15.1', '15.2', '15.3', '15.4']
|
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1', '26.1.1', '26.2', '26.3']
|
||||||
env:
|
env:
|
||||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
@@ -36,11 +36,11 @@ jobs:
|
|||||||
- name: Test
|
- name: Test
|
||||||
run: cd build ; ctest -j 10 --output-on-failure
|
run: cd build ; ctest -j 10 --output-on-failure
|
||||||
|
|
||||||
macos-15:
|
macos-26:
|
||||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1']
|
xcode: ['26.4.1', '26.5', '26.6']
|
||||||
env:
|
env:
|
||||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
@@ -71,3 +71,38 @@ jobs:
|
|||||||
run: cmake --build build --parallel 10
|
run: cmake --build build --parallel 10
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cd build ; ctest -j 10 --output-on-failure
|
run: cd build ; ctest -j 10 --output-on-failure
|
||||||
|
|
||||||
|
swiftpm:
|
||||||
|
runs-on: macos-15
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Check that Package.swift resolves without a deprecation warning
|
||||||
|
run: swift package dump-package
|
||||||
|
- name: Build the SwiftPM documentation example against this checkout
|
||||||
|
run: |
|
||||||
|
mkdir -p /tmp/json-swiftpm-consumer/Sources/MyLibrary
|
||||||
|
cp docs/mkdocs/docs/integration/swift/example.cpp /tmp/json-swiftpm-consumer/Sources/MyLibrary/example.cpp
|
||||||
|
cat > /tmp/json-swiftpm-consumer/Package.swift << EOF
|
||||||
|
// swift-tools-version: 5.9
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "MyPackage",
|
||||||
|
dependencies: [
|
||||||
|
.package(path: "${{ github.workspace }}")
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
.target(
|
||||||
|
name: "MyLibrary",
|
||||||
|
dependencies: [
|
||||||
|
.product(name: "json", package: "json")
|
||||||
|
],
|
||||||
|
publicHeadersPath: "."
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
EOF
|
||||||
|
cd /tmp/json-swiftpm-consumer
|
||||||
|
swift build
|
||||||
@@ -209,7 +209,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# older GCC docker images (4, 5, 6) fail to check out code
|
# older GCC docker images (4, 5, 6) fail to check out code
|
||||||
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', 'latest']
|
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', '16', 'latest']
|
||||||
container: gcc:${{ matrix.compiler }}
|
container: gcc:${{ matrix.compiler }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
|
||||||
Upstream-Name: json
|
|
||||||
Upstream-Contact: Niels Lohmann <mail@nlohmann.me>
|
|
||||||
Source: https://github.com/nlohmann/json
|
|
||||||
|
|
||||||
Files: *
|
|
||||||
Copyright: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
License: MIT
|
|
||||||
|
|
||||||
Files: include/nlohmann/thirdparty/hedley.hpp
|
|
||||||
Copyright: 2016-2021 Evan Nemerson <evan@nemerson.com>
|
|
||||||
License: CC0
|
|
||||||
|
|
||||||
Files: include/nlohmann/detail/meta/cpp_future.hpp
|
|
||||||
Copyright: 2013-2026 Niels Lohmann <https://nlohmann.me> and 2018 The Abseil Authors
|
|
||||||
License: MIT AND Apache-2.0
|
|
||||||
|
|
||||||
Files: tests/thirdparty/doctest/*
|
|
||||||
Copyright: 2016-2023 Viktor Kirilov
|
|
||||||
License: MIT
|
|
||||||
|
|
||||||
Files: tests/thirdparty/fifo_map/*
|
|
||||||
Copyright: 2015-2017 Niels Lohmann
|
|
||||||
License: MIT
|
|
||||||
|
|
||||||
Files: tests/thirdparty/imapdl/*
|
|
||||||
Copyright: 2017 Georg Sauthoff <mail@gms.tf>
|
|
||||||
License: GPL-3.0-only
|
|
||||||
|
|
||||||
Files: tools/amalgamate/*
|
|
||||||
Copyright: 2012 Erik Edlund <erik.edlund@32767.se>
|
|
||||||
License: BSD-3-Clause
|
|
||||||
|
|
||||||
Files: tools/gdb_pretty_printer/*
|
|
||||||
Copyright: 2020 Hannes Domani <https://github.com/ssbssa>
|
|
||||||
License: MIT
|
|
||||||
@@ -80,6 +80,7 @@ cc_library(
|
|||||||
name = "singleheader-json",
|
name = "singleheader-json",
|
||||||
hdrs = [
|
hdrs = [
|
||||||
"single_include/nlohmann/json.hpp",
|
"single_include/nlohmann/json.hpp",
|
||||||
|
"single_include/nlohmann/json_fwd.hpp",
|
||||||
],
|
],
|
||||||
includes = ["single_include"],
|
includes = ["single_include"],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
|
|||||||
+1
-1
@@ -10,5 +10,5 @@ title: "JSON for Modern C++"
|
|||||||
version: 3.12.0
|
version: 3.12.0
|
||||||
date-released: 2025-04-07
|
date-released: 2025-04-07
|
||||||
license: MIT
|
license: MIT
|
||||||
repository-code: "https://github.com/nlohmann"
|
repository-code: "https://github.com/nlohmann/json"
|
||||||
url: https://json.nlohmann.me
|
url: https://json.nlohmann.me
|
||||||
+4
-1
@@ -42,8 +42,11 @@ endif()
|
|||||||
## OPTIONS
|
## OPTIONS
|
||||||
##
|
##
|
||||||
|
|
||||||
|
# Build the tests by default only for the main project and only if the tests
|
||||||
|
# directory exists (the release archive json.tar.xz does not contain it).
|
||||||
# VERSION_GREATER_EQUAL is not available in older CMake (< 3.7)
|
# VERSION_GREATER_EQUAL is not available in older CMake (< 3.7)
|
||||||
if(${MAIN_PROJECT} AND (${CMAKE_VERSION} VERSION_EQUAL 3.13 OR ${CMAKE_VERSION} VERSION_GREATER 3.13))
|
if(${MAIN_PROJECT} AND (${CMAKE_VERSION} VERSION_EQUAL 3.13 OR ${CMAKE_VERSION} VERSION_GREATER 3.13)
|
||||||
|
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/CMakeLists.txt")
|
||||||
set(JSON_BuildTests_INIT ON)
|
set(JSON_BuildTests_INIT ON)
|
||||||
else()
|
else()
|
||||||
set(JSON_BuildTests_INIT OFF)
|
set(JSON_BuildTests_INIT OFF)
|
||||||
|
|||||||
@@ -196,19 +196,19 @@ Further documentation:
|
|||||||
|
|
||||||
## REUSE
|
## REUSE
|
||||||
|
|
||||||
### `.reuse/dep5`
|
### `REUSE.toml`
|
||||||
|
|
||||||
The file defines the licenses of certain third-party components in the repository. The root `Makefile` contains a target `reuse` that checks for compliance.
|
The file defines the licenses of certain third-party components in the repository. The root `Makefile` contains a target `reuse` that checks for compliance.
|
||||||
|
|
||||||
Further documentation:
|
Further documentation:
|
||||||
|
|
||||||
- [DEP5](https://reuse.software/spec-3.2/#dep5-deprecated)
|
- [REUSE.toml](https://reuse.software/spec-3.3/#reusetoml)
|
||||||
- [reuse command-line tool](https://pypi.org/project/reuse/)
|
- [reuse command-line tool](https://pypi.org/project/reuse/)
|
||||||
- [documentation of linting](https://reuse.readthedocs.io/en/stable/man/reuse-lint.html)
|
- [documentation of linting](https://reuse.readthedocs.io/en/stable/man/reuse-lint.html)
|
||||||
- [REUSE](http://reuse.software)
|
- [REUSE](http://reuse.software)
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> The filename `.reuse/dep5` is predetermined by REUSE. Alternatively, a `REUSE.toml` file can be used.
|
> The filename `REUSE.toml` is predetermined by REUSE. Alternatively, a `.reuse/dep5` file (deprecated) can be used.
|
||||||
|
|
||||||
### `.reuse/templates`
|
### `.reuse/templates`
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# find GNU sed to use `-i` parameter
|
# find GNU sed to use `-i` parameter
|
||||||
SED:=$(shell command -v gsed || which sed)
|
SED:=$(shell command -v gsed || which sed)
|
||||||
|
|
||||||
|
# find GNU tar to use `--sort` and `--pax-option` parameters
|
||||||
|
TAR:=$(shell command -v gtar || which tar)
|
||||||
|
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# source files
|
# source files
|
||||||
@@ -174,8 +177,8 @@ ChangeLog.md:
|
|||||||
# archive is created according to the advices of <https://reproducible-builds.org/docs/archives/>.
|
# archive is created according to the advices of <https://reproducible-builds.org/docs/archives/>.
|
||||||
json.tar.xz:
|
json.tar.xz:
|
||||||
mkdir json
|
mkdir json
|
||||||
rsync -R $(shell find LICENSE.MIT nlohmann_json.natvis CMakeLists.txt cmake/*.in include single_include -type f) json
|
rsync -R $(shell find LICENSE.MIT nlohmann_json.natvis CMakeLists.txt cmake/*.in include single_include src/modules -type f) json
|
||||||
gtar --sort=name --mtime="@$(shell git log -1 --pretty=%ct)" --owner=0 --group=0 --numeric-owner --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime --create --file - json | xz --compress -9e --threads=2 - > json.tar.xz
|
$(TAR) --sort=name --mtime="@$(shell git log -1 --pretty=%ct)" --owner=0 --group=0 --numeric-owner --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime --create --file - json | xz --compress -9e --threads=2 - > json.tar.xz
|
||||||
rm -fr json
|
rm -fr json
|
||||||
|
|
||||||
# We use `-X` to make the resulting ZIP file reproducible, see
|
# We use `-X` to make the resulting ZIP file reproducible, see
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ import PackageDescription
|
|||||||
let package = Package(
|
let package = Package(
|
||||||
name: "nlohmann-json",
|
name: "nlohmann-json",
|
||||||
platforms: [
|
platforms: [
|
||||||
.iOS(.v12), .macOS(.v10_13), .tvOS(.v12), .watchOS(.v4), .visionOS(.v1)
|
.iOS(.v12), .macOS(.v10_13), .tvOS(.v12), .watchOS(.v9), .visionOS(.v1)
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(name: "json", targets: ["json"])
|
.library(name: "json", targets: ["json"])
|
||||||
|
|||||||
@@ -1202,15 +1202,18 @@ language bindings, format converters, and the like. See the curated [Ecosystem](
|
|||||||
|
|
||||||
Though it's 2026 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:
|
Though it's 2026 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:
|
||||||
|
|
||||||
- GCC 4.8 - 14.2 (and possibly later)
|
- GCC 4.8 - 16.2 (and possibly later)
|
||||||
- Clang 3.4 - 21.0 (and possibly later)
|
- Clang 3.4 - 22.1 (and possibly later)
|
||||||
- Apple Clang 9.1 - 16.0 (and possibly later)
|
- Apple Clang 15.0 - 21.0 (and possibly later)
|
||||||
- Intel C++ Compiler 17.0.2 (and possibly later)
|
- Intel C++ Compiler Classic (icpc) 2021.10
|
||||||
- Nvidia CUDA Compiler 11.0.221 (and possibly later)
|
- Intel oneAPI DPC++/C++ Compiler (icpx) 2025.3 (and possibly later)
|
||||||
- Microsoft Visual C++ 2015 / Build Tools 14.0.25123.0 (and possibly later)
|
- NVIDIA CUDA Compiler (nvcc) 11.8 - 12.6 (and possibly later)
|
||||||
- Microsoft Visual C++ 2017 / Build Tools 15.5.180.51428 (and possibly later)
|
- NVIDIA HPC SDK C++ Compiler (nvc++) 25.5 (and possibly later)
|
||||||
- Microsoft Visual C++ 2019 / Build Tools 16.3.1+1def00d3d (and possibly later)
|
- Microsoft Visual C++ 2015 / MSVC 19.0 (and possibly later)
|
||||||
- Microsoft Visual C++ 2022 / Build Tools 19.30.30709.0 (and possibly later)
|
- Microsoft Visual C++ 2017 / MSVC 19.16 (and possibly later)
|
||||||
|
- Microsoft Visual C++ 2019 / MSVC 19.29 (and possibly later)
|
||||||
|
- Microsoft Visual C++ 2022 / MSVC 19.44 (and possibly later)
|
||||||
|
- Microsoft Visual C++ 2026 / MSVC 19.51 (and possibly later)
|
||||||
|
|
||||||
I would be happy to learn about other compilers/versions.
|
I would be happy to learn about other compilers/versions.
|
||||||
|
|
||||||
@@ -1401,7 +1404,7 @@ The library is compliant to version 3.3 of the [**REUSE specification**](https:/
|
|||||||
|
|
||||||
- Every source file contains an SPDX copyright header.
|
- Every source file contains an SPDX copyright header.
|
||||||
- The full text of all licenses used in the repository can be found in the `LICENSES` folder.
|
- The full text of all licenses used in the repository can be found in the `LICENSES` folder.
|
||||||
- File `.reuse/dep5` contains an overview of all files' copyrights and licenses.
|
- File `REUSE.toml` contains an overview of all files' copyrights and licenses.
|
||||||
- Run `pipx run reuse lint` to verify the project's REUSE compliance and `pipx run reuse spdx` to generate a SPDX SBOM.
|
- Run `pipx run reuse lint` to verify the project's REUSE compliance and `pipx run reuse spdx` to generate a SPDX SBOM.
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
version = 1
|
||||||
|
SPDX-PackageName = "json"
|
||||||
|
SPDX-PackageSupplier = "Niels Lohmann <mail@nlohmann.me>"
|
||||||
|
SPDX-PackageDownloadLocation = "https://github.com/nlohmann/json"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "**"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2013-2026 Niels Lohmann <https://nlohmann.me>"
|
||||||
|
SPDX-License-Identifier = "MIT"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "include/nlohmann/thirdparty/hedley.hpp"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2016-2021 Evan Nemerson <evan@nemerson.com>"
|
||||||
|
SPDX-License-Identifier = "CC0"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "include/nlohmann/detail/meta/cpp_future.hpp"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2013-2026 Niels Lohmann <https://nlohmann.me> and 2018 The Abseil Authors"
|
||||||
|
SPDX-License-Identifier = "MIT AND Apache-2.0"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "tests/thirdparty/doctest/**"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2016-2023 Viktor Kirilov"
|
||||||
|
SPDX-License-Identifier = "MIT"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "tests/thirdparty/fifo_map/**"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2015-2017 Niels Lohmann"
|
||||||
|
SPDX-License-Identifier = "MIT"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "tests/thirdparty/imapdl/**"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2017 Georg Sauthoff <mail@gms.tf>"
|
||||||
|
SPDX-License-Identifier = "GPL-3.0-only"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "tools/amalgamate/**"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2012 Erik Edlund <erik.edlund@32767.se>"
|
||||||
|
SPDX-License-Identifier = "BSD-3-Clause"
|
||||||
|
|
||||||
|
[[annotations]]
|
||||||
|
path = "tools/gdb_pretty_printer/**"
|
||||||
|
precedence = "aggregate"
|
||||||
|
SPDX-FileCopyrightText = "2020 Hannes Domani <https://github.com/ssbssa>"
|
||||||
|
SPDX-License-Identifier = "MIT"
|
||||||
@@ -48,6 +48,7 @@ cc_library(
|
|||||||
name = "singleheader-json",
|
name = "singleheader-json",
|
||||||
hdrs = [
|
hdrs = [
|
||||||
"single_include/nlohmann/json.hpp",
|
"single_include/nlohmann/json.hpp",
|
||||||
|
"single_include/nlohmann/json_fwd.hpp",
|
||||||
],
|
],
|
||||||
includes = ["single_include"],
|
includes = ["single_include"],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ if(MODE STREQUAL "undef")
|
|||||||
# recipe is self-contained and its output is byte-stable across reruns.
|
# recipe is self-contained and its output is byte-stable across reruns.
|
||||||
# The embedded SPDX tags below are part of the *generated* file's
|
# The embedded SPDX tags below are part of the *generated* file's
|
||||||
# content, not a REUSE header for this .cmake script itself (which is
|
# content, not a REUSE header for this .cmake script itself (which is
|
||||||
# already covered by the blanket "Files: *" rule in .reuse/dep5) -- keep
|
# already covered by the blanket path = "**" rule in REUSE.toml) -- keep
|
||||||
# them wrapped in REUSE-IgnoreStart/End so `reuse lint` does not try to
|
# 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.
|
# parse "MIT\n")" as this file's own SPDX-License-Identifier value.
|
||||||
# REUSE-IgnoreStart
|
# REUSE-IgnoreStart
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_string', 'Meth
|
|||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_ubjson', 'Function', 'api/basic_json/to_ubjson/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_ubjson', 'Function', 'api/basic_json/to_ubjson/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::value', 'Method', 'api/basic_json/value/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::value', 'Method', 'api/basic_json/value/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::value_t', 'Enum', 'api/basic_json/value_t/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::value_t', 'Enum', 'api/basic_json/value_t/index.html');
|
||||||
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::with_t', 'Type', 'api/basic_json/with_t/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::~basic_json', 'Method', 'api/basic_json/~basic_json/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::~basic_json', 'Method', 'api/basic_json/~basic_json/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('json', 'Class', 'api/json/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('json', 'Class', 'api/json/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer', 'Class', 'api/json_pointer/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer', 'Class', 'api/json_pointer/index.html');
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ The class satisfies the following concept requirements:
|
|||||||
- **initializer_list_t** - type for initializer lists of `basic_json` values
|
- **initializer_list_t** - type for initializer lists of `basic_json` values
|
||||||
- [**input_format_t**](input_format_t.md) - type to choose the format to parse
|
- [**input_format_t**](input_format_t.md) - type to choose the format to parse
|
||||||
- [**json_sax_t**](../json_sax/index.md) - type for SAX events
|
- [**json_sax_t**](../json_sax/index.md) - type for SAX events
|
||||||
|
- [**with_object_t, with_array_t, with_string_t, with_boolean_t, with_integers_t, with_float_t, with_allocator_t,
|
||||||
|
with_json_serializer_t, with_binary_t, with_base_class_t**](with_t.md) - types to create a `basic_json` type with
|
||||||
|
one (or two) replaced template parameters
|
||||||
|
|
||||||
### Exceptions
|
### Exceptions
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,14 @@ namespace std {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Return a hash value for a JSON object. The hash function tries to rely on `std::hash` where possible. Furthermore, the
|
Return a hash value for a JSON object. The hash function tries to rely on `std::hash` where possible. Furthermore, the
|
||||||
type of the JSON value is taken into account to have different hash values for `#!json null`, `#!cpp 0`, `#!cpp 0U`, and
|
type of the JSON value is taken into account, so `#!json null`, `#!cpp false`, and numbers may hash differently from
|
||||||
`#!cpp false`, etc.
|
each other. Numbers that compare equal under [`operator==`](operator_eq.md) always hash equally, regardless of
|
||||||
|
whether they are stored as signed integer, unsigned integer, or floating-point number.
|
||||||
|
|
||||||
|
Numbers are hashed by their value converted to `number_float_t`. Converting an integer to `number_float_t` therefore
|
||||||
|
keeps its hash, but converting a floating-point number to an integer type is lossy and can change it: `#!cpp 0.5`
|
||||||
|
converts to `#!cpp 0`, which need not have the same hash. Unequal numbers can also share a hash value, for example two
|
||||||
|
large integers that convert to the same `number_float_t`.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -26,7 +32,8 @@ type of the JSON value is taken into account to have different hash values for `
|
|||||||
--8<-- "examples/std_hash.output"
|
--8<-- "examples/std_hash.output"
|
||||||
```
|
```
|
||||||
|
|
||||||
Note the output is platform-dependent.
|
The hash values shown are examples only. They depend on the platform, the compiler, and the compiler version, and
|
||||||
|
they can change between versions of this library. Do not persist them or rely on specific values.
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
@@ -36,3 +43,5 @@ type of the JSON value is taken into account to have different hash values for `
|
|||||||
|
|
||||||
- Added in version 1.0.0.
|
- Added in version 1.0.0.
|
||||||
- Extended for arbitrary basic_json types in version 3.10.5.
|
- Extended for arbitrary basic_json types in version 3.10.5.
|
||||||
|
- Numbers that compare equal hash equally since version 3.13.0; before, `#!cpp 0`, `#!cpp 0U`, and `#!cpp 0.0` had
|
||||||
|
different hash values.
|
||||||
@@ -68,6 +68,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||||
|
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||||
|
discarded; example: `"cannot serialize discarded value to BJData"`
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -120,3 +122,5 @@ Linear in the size of the JSON value `j`.
|
|||||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||||
|
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||||
|
array or object was silently skipped, producing invalid BJData.
|
||||||
@@ -58,6 +58,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is
|
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is
|
||||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||||
|
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if a value nested in `j` is discarded
|
||||||
|
(the top-level value itself is covered by `type_error.317` above, since it must be an object); example:
|
||||||
|
`"cannot serialize discarded value to BSON"`
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -110,6 +113,8 @@ pass before anything is written.
|
|||||||
- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0.
|
- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0.
|
||||||
- Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0.
|
- Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0.
|
||||||
- `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0.
|
- `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0.
|
||||||
|
- Throws `type_error.321` for a discarded value nested in `j` since version 3.13.0; previously, it was silently
|
||||||
|
skipped, producing a document whose declared size did not match what was actually written.
|
||||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||||
|
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||||
|
discarded; example: `"cannot serialize discarded value to CBOR"`
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -86,3 +88,5 @@ Linear in the size of the JSON value `j`.
|
|||||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||||
|
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||||
|
array or object was silently skipped, producing invalid CBOR.
|
||||||
@@ -54,6 +54,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
|
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
|
||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||||
not valid UTF-8 and `error_handler` is `strict`
|
not valid UTF-8 and `error_handler` is `strict`
|
||||||
|
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||||
|
discarded; example: `"cannot serialize discarded value to MessagePack"`
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -108,3 +110,5 @@ Linear in the size of the JSON value `j`.
|
|||||||
- Fixed in version 3.13.0 to serialize `number_integer_t`/`number_unsigned_t` pairs of different width correctly;
|
- Fixed in version 3.13.0 to serialize `number_integer_t`/`number_unsigned_t` pairs of different width correctly;
|
||||||
before, integers could be serialized with the wrong value if `number_integer_t` was narrower than
|
before, integers could be serialized with the wrong value if `number_integer_t` was narrower than
|
||||||
`number_unsigned_t`.
|
`number_unsigned_t`.
|
||||||
|
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||||
|
array or object was silently skipped, producing invalid MessagePack.
|
||||||
@@ -61,6 +61,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||||
|
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||||
|
discarded; example: `"cannot serialize discarded value to UBJSON"`
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -112,3 +114,5 @@ Linear in the size of the JSON value `j`.
|
|||||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||||
|
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||||
|
array or object was silently skipped, producing invalid UBJSON.
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
# <small>nlohmann::basic_json::</small>with_t
|
||||||
|
|
||||||
|
Member alias templates `with_object_t`, `with_array_t`, `with_string_t`, `with_boolean_t`, `with_integers_t`,
|
||||||
|
`with_float_t`, `with_allocator_t`, `with_json_serializer_t`, `with_binary_t`, and `with_base_class_t`.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template<template<typename, typename, typename...> class ObjectType2>
|
||||||
|
using with_object_t = basic_json<ObjectType2, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
||||||
|
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
template<template<typename, typename...> class ArrayType2>
|
||||||
|
using with_array_t = basic_json<ObjectType, ArrayType2, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
||||||
|
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
template<class StringType2>
|
||||||
|
using with_string_t = basic_json<ObjectType, ArrayType, StringType2, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
||||||
|
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
template<class BooleanType2>
|
||||||
|
using with_boolean_t = basic_json<ObjectType, ArrayType, StringType, BooleanType2,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
||||||
|
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
template<class NumberIntegerType2, class NumberUnsignedType2>
|
||||||
|
using with_integers_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType2, NumberUnsignedType2, NumberFloatType,
|
||||||
|
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
template<class NumberFloatType2>
|
||||||
|
using with_float_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType2,
|
||||||
|
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
template<template<typename> class AllocatorType2>
|
||||||
|
using with_allocator_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
||||||
|
AllocatorType2, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
template<template<typename, typename = void> class JSONSerializer2>
|
||||||
|
using with_json_serializer_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
||||||
|
AllocatorType, JSONSerializer2, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
template<class BinaryType2>
|
||||||
|
using with_binary_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
||||||
|
AllocatorType, JSONSerializer, BinaryType2, CustomBaseClass>;
|
||||||
|
|
||||||
|
template<class CustomBaseClass2>
|
||||||
|
using with_base_class_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
||||||
|
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass2>;
|
||||||
|
```
|
||||||
|
|
||||||
|
These member alias templates make it easier to create a `basic_json` type that is identical to the current type except
|
||||||
|
for one (or, in the case of `with_integers_t`, two) of its [template parameters](index.md#template-parameters).
|
||||||
|
Spelling out all 11 template parameters of `basic_json` just to change a single one is verbose and error-prone; these
|
||||||
|
aliases only require the replacement type(s).
|
||||||
|
|
||||||
|
with_object_t<ObjectType2>
|
||||||
|
: replaces `ObjectType`
|
||||||
|
|
||||||
|
with_array_t<ArrayType2>
|
||||||
|
: replaces `ArrayType`
|
||||||
|
|
||||||
|
with_string_t<StringType2>
|
||||||
|
: replaces `StringType`
|
||||||
|
|
||||||
|
with_boolean_t<BooleanType2>
|
||||||
|
: replaces `BooleanType`
|
||||||
|
|
||||||
|
with_integers_t<NumberIntegerType2, NumberUnsignedType2>
|
||||||
|
: replaces both `NumberIntegerType` and `NumberUnsignedType`; the two are combined into a single alias because they
|
||||||
|
are usually changed together (for instance, when switching to fixed-width integer types)
|
||||||
|
|
||||||
|
with_float_t<NumberFloatType2>
|
||||||
|
: replaces `NumberFloatType`
|
||||||
|
|
||||||
|
with_allocator_t<AllocatorType2>
|
||||||
|
: replaces `AllocatorType`
|
||||||
|
|
||||||
|
with_json_serializer_t<JSONSerializer2>
|
||||||
|
: replaces `JSONSerializer`
|
||||||
|
|
||||||
|
with_binary_t<BinaryType2>
|
||||||
|
: replaces `BinaryType`
|
||||||
|
|
||||||
|
with_base_class_t<CustomBaseClass2>
|
||||||
|
: replaces `CustomBaseClass`; see also [`json_base_class_t`](json_base_class_t.md)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
All other template parameters are kept unchanged, so the resulting type still uses, for instance, the same
|
||||||
|
`ObjectType` unless `with_object_t` itself is used.
|
||||||
|
|
||||||
|
The aliases are members of every `basic_json` specialization, including [`ordered_json`](../ordered_json.md), and the
|
||||||
|
type they produce is again a `basic_json` specialization. They can therefore be chained to replace several template
|
||||||
|
parameters at once:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using my_json = nlohmann::json::with_integers_t<int, unsigned int>::with_float_t<float>;
|
||||||
|
using my_ordered_json = nlohmann::ordered_json::with_string_t<std::wstring>;
|
||||||
|
```
|
||||||
|
|
||||||
|
The result is the same type as spelling out all template parameters, so the order of the chained aliases does not
|
||||||
|
matter. For instance, `nlohmann::json::with_object_t<nlohmann::ordered_map>` is `nlohmann::ordered_json`.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
??? example
|
||||||
|
|
||||||
|
The following code shows how `with_object_t` can be used to create a JSON type that stores object elements in a
|
||||||
|
`std::map` and therefore keeps them sorted by key, unlike the default type which preserves insertion order
|
||||||
|
only when `nlohmann::ordered_json` is used.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
--8<-- "examples/with_t.cpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
--8<-- "examples/with_t.output"
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [basic_json](index.md#template-parameters) - the template parameters that can be replaced
|
||||||
|
- [json_base_class_t](json_base_class_t.md) - the type used for `CustomBaseClass`
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -21,17 +21,18 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
|||||||
|
|
||||||
| Compiler | Architecture | Operating System | CI |
|
| Compiler | Architecture | Operating System | CI |
|
||||||
|----------------------------------------------|--------------|-----------------------------------|-----------|
|
|----------------------------------------------|--------------|-----------------------------------|-----------|
|
||||||
| AppleClang 15.0.0.15000040; Xcode 15.0.1 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
|
||||||
| AppleClang 15.0.0.15000100; Xcode 15.1 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
|
||||||
| AppleClang 15.0.0.15000100; Xcode 15.2 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
|
||||||
| AppleClang 15.0.0.15000309; Xcode 15.3 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
|
||||||
| AppleClang 15.0.0.15000309; Xcode 15.4 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
|
||||||
| AppleClang 16.0.0.16000026; Xcode 16 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
| AppleClang 16.0.0.16000026; Xcode 16 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||||
| AppleClang 16.0.0.16000026; Xcode 16.1 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
| AppleClang 16.0.0.16000026; Xcode 16.1 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||||
| AppleClang 16.0.0.16000026; Xcode 16.2 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
| AppleClang 16.0.0.16000026; Xcode 16.2 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||||
| AppleClang 17.0.0.17000013; Xcode 16.3 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
| AppleClang 17.0.0.17000013; Xcode 16.3 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
||||||
| AppleClang 17.0.0.17000013; Xcode 16.4 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
| AppleClang 17.0.0.17000013; Xcode 16.4 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
||||||
| AppleClang 17.0.0.17000319; Xcode 26.0.1 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
| AppleClang 17.0.0.17000319; Xcode 26.0.1 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
||||||
|
| AppleClang 17.0.0.17000404; Xcode 26.1.1 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
|
||||||
|
| AppleClang 17.0.0.17000603; Xcode 26.2 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
|
||||||
|
| AppleClang 17.0.0.17000604; Xcode 26.3 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
|
||||||
|
| AppleClang 21.0.0.21000099; Xcode 26.4.1 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
|
||||||
|
| AppleClang 21.0.0.21000101; Xcode 26.5 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
|
||||||
|
| AppleClang 21.0.0.21000101; Xcode 26.6 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
|
||||||
| Clang 3.4.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| Clang 3.4.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| Clang 3.5.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| Clang 3.5.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| Clang 3.6.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| Clang 3.6.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
@@ -89,7 +90,7 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
|||||||
| GNU 13.3.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| GNU 13.3.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| GNU 14.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| GNU 14.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| GNU 15.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| GNU 15.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| GNU 16.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| GNU 16.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| GNU 16.1.0 | arm64 | Ubuntu 24.04 | GitHub |
|
| GNU 16.1.0 | arm64 | Ubuntu 24.04 | GitHub |
|
||||||
| icpc (ICC) 2021.10.0 20230609 | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
| icpc (ICC) 2021.10.0 20230609 | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
||||||
| icpx (Intel oneAPI DPC++/C++) 2025.3.2 | x86_64 | Ubuntu 24.04 LTS | GitHub |
|
| icpx (Intel oneAPI DPC++/C++) 2025.3.2 | x86_64 | Ubuntu 24.04 LTS | GitHub |
|
||||||
|
|||||||
@@ -13,19 +13,7 @@ class visitor_adaptor_with_metadata
|
|||||||
void do_visit(const Ptr& ptr, const Fnc& fnc) const;
|
void do_visit(const Ptr& ptr, const Fnc& fnc) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
using json = nlohmann::basic_json <
|
using json = nlohmann::json::with_base_class_t<visitor_adaptor_with_metadata>;
|
||||||
std::map,
|
|
||||||
std::vector,
|
|
||||||
std::string,
|
|
||||||
bool,
|
|
||||||
std::int64_t,
|
|
||||||
std::uint64_t,
|
|
||||||
double,
|
|
||||||
std::allocator,
|
|
||||||
nlohmann::adl_serializer,
|
|
||||||
std::vector<std::uint8_t>,
|
|
||||||
visitor_adaptor_with_metadata
|
|
||||||
>;
|
|
||||||
|
|
||||||
template <class Fnc>
|
template <class Fnc>
|
||||||
void visitor_adaptor_with_metadata::visit(const Fnc& fnc) const
|
void visitor_adaptor_with_metadata::visit(const Fnc& fnc) const
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ int main()
|
|||||||
<< "hash(false) = " << std::hash<json> {}(json(false)) << '\n'
|
<< "hash(false) = " << std::hash<json> {}(json(false)) << '\n'
|
||||||
<< "hash(0) = " << std::hash<json> {}(json(0)) << '\n'
|
<< "hash(0) = " << std::hash<json> {}(json(0)) << '\n'
|
||||||
<< "hash(0U) = " << std::hash<json> {}(json(0U)) << '\n'
|
<< "hash(0U) = " << std::hash<json> {}(json(0U)) << '\n'
|
||||||
|
<< "hash(0.0) = " << std::hash<json> {}(json(0.0)) << '\n'
|
||||||
<< "hash(\"\") = " << std::hash<json> {}(json("")) << '\n'
|
<< "hash(\"\") = " << std::hash<json> {}(json("")) << '\n'
|
||||||
<< "hash({}) = " << std::hash<json> {}(json::object()) << '\n'
|
<< "hash({}) = " << std::hash<json> {}(json::object()) << '\n'
|
||||||
<< "hash([]) = " << std::hash<json> {}(json::array()) << '\n'
|
<< "hash([]) = " << std::hash<json> {}(json::array()) << '\n'
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
hash(null) = 2654435769
|
hash(null) = 2654435769
|
||||||
hash(false) = 2654436030
|
hash(false) = 2654436030
|
||||||
hash(0) = 2654436095
|
hash(0) = 2654436221
|
||||||
hash(0U) = 2654436156
|
hash(0U) = 2654436221
|
||||||
hash("") = 6142509191626859748
|
hash(0.0) = 2654436221
|
||||||
|
hash("") = 11160318156688833227
|
||||||
hash({}) = 2654435832
|
hash({}) = 2654435832
|
||||||
hash([]) = 2654435899
|
hash([]) = 2654435899
|
||||||
hash({"hello": "world"}) = 4469488738203676328
|
hash({"hello": "world"}) = 3701319991624763853
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
// a JSON type that stores objects in a std::map (which keeps keys sorted)
|
||||||
|
// instead of the default ordered associative container
|
||||||
|
using sorted_json = nlohmann::json::with_object_t<std::map>;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
sorted_json j;
|
||||||
|
j["c"] = 1;
|
||||||
|
j["a"] = 2;
|
||||||
|
j["b"] = 3;
|
||||||
|
|
||||||
|
// keys are sorted, because std::map is used to store the object
|
||||||
|
std::cout << j.dump() << std::endl;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"a":2,"b":3,"c":1}
|
||||||
@@ -804,6 +804,20 @@ does not list an enumerator and it is therefore converted like the first listed
|
|||||||
[json.exception.type_error.318] duplicate object key 'red'
|
[json.exception.type_error.318] duplicate object key 'red'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### json.exception.type_error.321
|
||||||
|
|
||||||
|
A discarded value (one created by [`parse()`](../api/basic_json/parse.md) with a callback that returns `false` for the
|
||||||
|
value, or by default-constructing a [`basic_json`](../api/basic_json/index.md) with
|
||||||
|
[`value_t::discarded`](../api/basic_json/value_t.md)) was passed to a binary serialization function, either directly or
|
||||||
|
nested in an array or object. There is no way to represent a discarded value in CBOR, MessagePack, UBJSON, BJData, or BSON.
|
||||||
|
|
||||||
|
!!! failure "Example message"
|
||||||
|
|
||||||
|
Serializing `#!json [1, 2]` to CBOR, where the second element was discarded by a parser callback:
|
||||||
|
```
|
||||||
|
[json.exception.type_error.321] cannot serialize discarded value to CBOR
|
||||||
|
```
|
||||||
|
|
||||||
## Out of range
|
## Out of range
|
||||||
|
|
||||||
This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys.
|
This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys.
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ automatically download a release as a dependency at configure time.
|
|||||||
|
|
||||||
### `JSON_BuildTests`
|
### `JSON_BuildTests`
|
||||||
|
|
||||||
Build the unit tests when [`BUILD_TESTING`](https://cmake.org/cmake/help/latest/command/enable_testing.html) is enabled. This option is `ON` by default if the library's CMake project is the top project. That is, when integrating the library as described above, the test suite is not built unless explicitly switched on with this option.
|
Build the unit tests when [`BUILD_TESTING`](https://cmake.org/cmake/help/latest/command/enable_testing.html) is enabled. This option is `ON` by default if the library's CMake project is the top project and the `tests` directory exists (the release archive `json.tar.xz` does not contain it). That is, when integrating the library as described above, the test suite is not built unless explicitly switched on with this option.
|
||||||
|
|
||||||
### `JSON_CI`
|
### `JSON_CI`
|
||||||
|
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ nav:
|
|||||||
- 'update': api/basic_json/update.md
|
- 'update': api/basic_json/update.md
|
||||||
- 'value': api/basic_json/value.md
|
- 'value': api/basic_json/value.md
|
||||||
- 'value_t': api/basic_json/value_t.md
|
- 'value_t': api/basic_json/value_t.md
|
||||||
|
- 'with_t': api/basic_json/with_t.md
|
||||||
- byte_container_with_subtype:
|
- byte_container_with_subtype:
|
||||||
- 'Overview': api/byte_container_with_subtype/index.md
|
- 'Overview': api/byte_container_with_subtype/index.md
|
||||||
- '(constructor)': api/byte_container_with_subtype/byte_container_with_subtype.md
|
- '(constructor)': api/byte_container_with_subtype/byte_container_with_subtype.md
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ mkdocs-material==9.7.7 # theme for mkdocs
|
|||||||
mkdocs-material-extensions==1.3.1 # extensions
|
mkdocs-material-extensions==1.3.1 # extensions
|
||||||
mkdocs-minify-plugin==0.8.0 # plugin "minify"
|
mkdocs-minify-plugin==0.8.0 # plugin "minify"
|
||||||
mkdocs-redirects==1.2.3 # plugin "redirects"
|
mkdocs-redirects==1.2.3 # plugin "redirects"
|
||||||
mkdocs-htmlproofer-plugin==1.5.0 # plugin "htmlproofer"
|
mkdocs-htmlproofer-plugin==1.6.0 # plugin "htmlproofer"
|
||||||
mkdocs-llmstxt==0.5.0 # plugin "llmstxt"
|
mkdocs-llmstxt==0.5.0 # plugin "llmstxt"
|
||||||
|
|
||||||
PyYAML==6.0.3 # linter
|
PyYAML==6.0.3 # linter
|
||||||
+3
-3
@@ -1454,9 +1454,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
|
||||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
"integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ std::size_t hash_iteratively(const BasicJsonType& j);
|
|||||||
@brief hash a JSON value
|
@brief hash a JSON value
|
||||||
|
|
||||||
The hash function tries to rely on std::hash where possible. Furthermore, the
|
The hash function tries to rely on std::hash where possible. Furthermore, the
|
||||||
type of the JSON value is taken into account to have different hash values for
|
type of the JSON value is taken into account, so null, false, and numbers may
|
||||||
null, 0, 0U, and false, etc.
|
hash differently from each other, but any two numbers that compare equal
|
||||||
|
under operator== hash equally regardless of which of number_integer,
|
||||||
|
number_unsigned, or number_float actually holds the value.
|
||||||
|
|
||||||
Hashing an array or an object hashes its elements, which used to call this
|
Hashing an array or an object hashes its elements, which used to call this
|
||||||
function again once per nesting level, so a value nested deeply enough
|
function again once per nesting level, so a value nested deeply enough
|
||||||
@@ -55,8 +57,6 @@ template<typename BasicJsonType>
|
|||||||
std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
|
std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
|
||||||
{
|
{
|
||||||
using string_t = typename BasicJsonType::string_t;
|
using string_t = typename BasicJsonType::string_t;
|
||||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
|
||||||
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
|
||||||
using number_float_t = typename BasicJsonType::number_float_t;
|
using number_float_t = typename BasicJsonType::number_float_t;
|
||||||
|
|
||||||
const auto type = static_cast<std::size_t>(j.type());
|
const auto type = static_cast<std::size_t>(j.type());
|
||||||
@@ -113,21 +113,24 @@ std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case BasicJsonType::value_t::number_integer:
|
case BasicJsonType::value_t::number_integer:
|
||||||
{
|
|
||||||
const auto h = std::hash<number_integer_t> {}(j.template get<number_integer_t>());
|
|
||||||
return combine(type, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
case BasicJsonType::value_t::number_unsigned:
|
case BasicJsonType::value_t::number_unsigned:
|
||||||
{
|
|
||||||
const auto h = std::hash<number_unsigned_t> {}(j.template get<number_unsigned_t>());
|
|
||||||
return combine(type, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
case BasicJsonType::value_t::number_float:
|
case BasicJsonType::value_t::number_float:
|
||||||
{
|
{
|
||||||
const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());
|
// operator== compares numbers by their mathematical value across
|
||||||
return combine(type, h);
|
// number_integer, number_unsigned, and number_float, so equal
|
||||||
|
// numbers of different internal types (0, 0U, 0.0) must hash the
|
||||||
|
// same. Two equal numbers have the same value, which converts to
|
||||||
|
// the same number_float_t, so all numbers share one type tag and
|
||||||
|
// hash that converted value. Adding zero turns -0.0 (equal to 0)
|
||||||
|
// into 0.0, as std::hash need not map both to the same hash.
|
||||||
|
// The converse does not hold: converting a number_float_t value
|
||||||
|
// to an integer type is lossy, so the result can hash
|
||||||
|
// differently, and unequal numbers that convert to the same
|
||||||
|
// number_float_t (e.g., 2^53 and 2^53 + 1) share a hash.
|
||||||
|
const auto number_type = static_cast<std::size_t>(BasicJsonType::value_t::number_float);
|
||||||
|
const auto value = j.template get<number_float_t>() + static_cast<number_float_t>(0);
|
||||||
|
const auto h = std::hash<number_float_t> {}(value);
|
||||||
|
return combine(number_type, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
case BasicJsonType::value_t::binary:
|
case BasicJsonType::value_t::binary:
|
||||||
|
|||||||
@@ -3249,6 +3249,12 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// the lexer would stop at a NUL and accept the digits before it
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(current == '\0'))
|
||||||
|
{
|
||||||
|
return sax->parse_error(chars_read, "00", parse_error::create(115, chars_read,
|
||||||
|
exception_message("invalid number text; last byte: 0x00", "high-precision number"), nullptr));
|
||||||
|
}
|
||||||
number_vector.push_back(static_cast<char>(current));
|
number_vector.push_back(static_cast<char>(current));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2021,6 +2021,39 @@ scan_number_done:
|
|||||||
// read the next character and ignore whitespace
|
// read the next character and ignore whitespace
|
||||||
skip_whitespace();
|
skip_whitespace();
|
||||||
|
|
||||||
|
return scan_after_whitespace();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief scan the next token when the caller expects a separator (':' or
|
||||||
|
',') most of the time
|
||||||
|
|
||||||
|
After an object key the next token is almost always ':', after a value
|
||||||
|
inside an object or array almost always ','. Testing for that character
|
||||||
|
first is a compare and a well-predicted branch, where the switch in
|
||||||
|
scan_after_whitespace() is an indirect jump through a table. Anything else
|
||||||
|
goes through the switch, so the result is the same as scan()'s.
|
||||||
|
|
||||||
|
May only be called after scan() has run once (the BOM check is skipped).
|
||||||
|
*/
|
||||||
|
token_type scan_expecting(token_type expected_type)
|
||||||
|
{
|
||||||
|
JSON_ASSERT(expected_type == token_type::name_separator || expected_type == token_type::value_separator);
|
||||||
|
JSON_ASSERT(position.chars_read_total > 0);
|
||||||
|
const char_int_type expected_char = static_cast<unsigned char>((expected_type == token_type::name_separator) ? ':' : ',');
|
||||||
|
skip_whitespace();
|
||||||
|
if (JSON_HEDLEY_LIKELY(current == expected_char))
|
||||||
|
{
|
||||||
|
return expected_type;
|
||||||
|
}
|
||||||
|
return scan_after_whitespace();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// the part of scan() after the leading whitespace: skip comments and
|
||||||
|
/// scan the token that starts with current
|
||||||
|
token_type scan_after_whitespace()
|
||||||
|
{
|
||||||
// ignore comments
|
// ignore comments
|
||||||
while (ignore_comments && current == '/')
|
while (ignore_comments && current == '/')
|
||||||
{
|
{
|
||||||
@@ -2100,7 +2133,6 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
/// input adapter
|
/// input adapter
|
||||||
InputAdapterType ia;
|
InputAdapterType ia;
|
||||||
|
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -423,7 +423,7 @@ class parser
|
|||||||
{
|
{
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of array (ignore_trailing_commas = true)
|
// or end of array (ignore_trailing_commas = true)
|
||||||
if (get_token() == token_type::value_separator)
|
if (get_token_expecting(token_type::value_separator))
|
||||||
{
|
{
|
||||||
// parse a new value
|
// parse a new value
|
||||||
get_token();
|
get_token();
|
||||||
@@ -463,7 +463,7 @@ class parser
|
|||||||
|
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of object (ignore_trailing_commas = true)
|
// or end of object (ignore_trailing_commas = true)
|
||||||
if (get_token() == token_type::value_separator)
|
if (get_token_expecting(token_type::value_separator))
|
||||||
{
|
{
|
||||||
get_token();
|
get_token();
|
||||||
|
|
||||||
@@ -484,7 +484,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -528,6 +528,13 @@ class parser
|
|||||||
return last_token = m_lexer.scan();
|
return last_token = m_lexer.scan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// get next token from lexer; true if it is the separator @a expected_type
|
||||||
|
/// (name_separator or value_separator), which it usually is
|
||||||
|
bool get_token_expecting(token_type expected_type)
|
||||||
|
{
|
||||||
|
return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type;
|
||||||
|
}
|
||||||
|
|
||||||
std::string exception_message(const token_type expected, const std::string& context)
|
std::string exception_message(const token_type expected, const std::string& context)
|
||||||
{
|
{
|
||||||
std::string error_msg = "syntax error ";
|
std::string error_msg = "syntax error ";
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ class binary_writer
|
|||||||
@throw type_error.316 if a string value or an object key is not valid
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
@throw type_error.317 if @a j is not an object
|
@throw type_error.317 if @a j is not an object
|
||||||
|
@throw type_error.321 if a value nested in @a j is discarded
|
||||||
*/
|
*/
|
||||||
void write_bson(const BasicJsonType& j)
|
void write_bson(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -158,6 +159,7 @@ class binary_writer
|
|||||||
@param[in] j JSON value to serialize
|
@param[in] j JSON value to serialize
|
||||||
@throw type_error.316 if a string value or an object key is not valid
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
|
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||||
*/
|
*/
|
||||||
void write_cbor(const BasicJsonType& j)
|
void write_cbor(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -322,7 +324,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, "CBOR");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,6 +384,7 @@ class binary_writer
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] j JSON value to serialize
|
@param[in] j JSON value to serialize
|
||||||
|
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||||
*/
|
*/
|
||||||
void write_msgpack(const BasicJsonType& j)
|
void write_msgpack(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -655,7 +658,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, "MessagePack");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -668,6 +671,7 @@ class binary_writer
|
|||||||
@param[in] bjdata_version which BJData version to use, default is draft2
|
@param[in] bjdata_version which BJData version to use, default is draft2
|
||||||
@throw type_error.316 if a string value or an object key is not valid
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
|
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||||
*/
|
*/
|
||||||
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||||
const bool use_type, const bool add_prefix = true,
|
const bool use_type, const bool add_prefix = true,
|
||||||
@@ -901,7 +905,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -921,6 +925,15 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/*!
|
||||||
|
@brief throws because @a j is discarded and cannot be serialized
|
||||||
|
@throw type_error.321 always
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
|
||||||
|
{
|
||||||
|
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
|
||||||
|
}
|
||||||
|
|
||||||
//////////
|
//////////
|
||||||
// BSON //
|
// BSON //
|
||||||
//////////
|
//////////
|
||||||
@@ -1172,6 +1185,7 @@ class binary_writer
|
|||||||
into a byte, before anything is written
|
into a byte, before anything is written
|
||||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||||
anything is written
|
anything is written
|
||||||
|
@throw type_error.321 if @a j is discarded
|
||||||
*/
|
*/
|
||||||
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -1198,10 +1212,12 @@ class binary_writer
|
|||||||
case value_t::null:
|
case value_t::null:
|
||||||
return 0ul;
|
return 0ul;
|
||||||
|
|
||||||
|
case value_t::discarded:
|
||||||
|
throw_on_discarded(j, "BSON");
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
return 0ul;
|
return 0ul;
|
||||||
@@ -1238,10 +1254,12 @@ class binary_writer
|
|||||||
case value_t::null:
|
case value_t::null:
|
||||||
return write_bson_null(name);
|
return write_bson_null(name);
|
||||||
|
|
||||||
|
case value_t::discarded:
|
||||||
|
throw_on_discarded(j, "BSON");
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
return;
|
return;
|
||||||
@@ -1308,6 +1326,8 @@ class binary_writer
|
|||||||
byte, before anything is written
|
byte, before anything is written
|
||||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||||
before anything is written
|
before anything is written
|
||||||
|
@throw type_error.321 if a value nested in @a document is discarded,
|
||||||
|
before anything is written
|
||||||
*/
|
*/
|
||||||
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
||||||
{
|
{
|
||||||
|
|||||||
+282
-73
@@ -226,6 +226,70 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// SAX interface type, see @ref nlohmann::json_sax
|
/// SAX interface type, see @ref nlohmann::json_sax
|
||||||
using json_sax_t = json_sax<basic_json>;
|
using json_sax_t = json_sax<basic_json>;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// utility templates to create a json type with different template parameters //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Json type using a different type for storing objects
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<template<typename, typename, typename...> class ObjectType2>
|
||||||
|
using with_object_t = basic_json<ObjectType2, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type for storing arrays
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<template<typename, typename...> class ArrayType2>
|
||||||
|
using with_array_t = basic_json<ObjectType, ArrayType2, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type for storing strings
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<class StringType2>
|
||||||
|
using with_string_t = basic_json<ObjectType, ArrayType, StringType2, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type for storing booleans
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<class BooleanType2>
|
||||||
|
using with_boolean_t = basic_json<ObjectType, ArrayType, StringType, BooleanType2,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using different types for storing signed and unsigned integers
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<class NumberIntegerType2, class NumberUnsignedType2>
|
||||||
|
using with_integers_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType2, NumberUnsignedType2, NumberFloatType, AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type for storing floating point numbers
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<class NumberFloatType2>
|
||||||
|
using with_float_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType2, AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type as base allocator
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<template<typename> class AllocatorType2>
|
||||||
|
using with_allocator_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType2, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type as json serializer
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<template<typename, typename = void> class JSONSerializer2>
|
||||||
|
using with_json_serializer_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer2, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type for storing binary data
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<class BinaryType2>
|
||||||
|
using with_binary_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType2, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type as base class
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<class CustomBaseClass2>
|
||||||
|
using with_base_class_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType, CustomBaseClass2>;
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// exceptions //
|
// exceptions //
|
||||||
////////////////
|
////////////////
|
||||||
@@ -612,100 +676,247 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// constructor for rvalue binary arrays (internal type)
|
/// constructor for rvalue binary arrays (internal type)
|
||||||
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
||||||
|
|
||||||
void destroy(value_t t)
|
private:
|
||||||
|
// raw, allocation-free transfer of m_data from src to dst: no
|
||||||
|
// set_parents()/assert_invariant() (the former is O(#children) per
|
||||||
|
// call under JSON_DIAGNOSTICS, which would make the walk below
|
||||||
|
// quadratic); dst takes ownership, src is left as value_t::null.
|
||||||
|
static void take(basic_json& dst, basic_json& src) noexcept
|
||||||
|
{
|
||||||
|
dst.m_data.m_type = src.m_data.m_type;
|
||||||
|
dst.m_data.m_value = src.m_data.m_value;
|
||||||
|
src.m_data.m_type = value_t::null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// true if v is not an array/object, or is an already-empty one
|
||||||
|
static bool has_no_children(const basic_json& v) noexcept
|
||||||
|
{
|
||||||
|
switch (v.m_data.m_type)
|
||||||
|
{
|
||||||
|
case value_t::array:
|
||||||
|
return v.m_data.m_value.array->empty();
|
||||||
|
case value_t::object:
|
||||||
|
return v.m_data.m_value.object->empty();
|
||||||
|
case value_t::null:
|
||||||
|
case value_t::string:
|
||||||
|
case value_t::boolean:
|
||||||
|
case value_t::number_integer:
|
||||||
|
case value_t::number_unsigned:
|
||||||
|
case value_t::number_float:
|
||||||
|
case value_t::binary:
|
||||||
|
case value_t::discarded:
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The walk in destroy_container() may take the children of a
|
||||||
|
// container in any order, as long as it picks the same child again
|
||||||
|
// while that container is not modified in between. Arrays and
|
||||||
|
// objects with bidirectional iterators (std::map, ordered_map, ...)
|
||||||
|
// use their last child, which a vector-based container can remove
|
||||||
|
// in O(1). ObjectType only needs forward iterators, though (e.g.
|
||||||
|
// std::unordered_map), so other objects use their first child.
|
||||||
|
template<typename ObjectType_>
|
||||||
|
static typename ObjectType_::iterator walk_child_it(ObjectType_& o, std::bidirectional_iterator_tag /*unused*/)
|
||||||
|
{
|
||||||
|
return std::prev(o.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ObjectType_>
|
||||||
|
static typename ObjectType_::iterator walk_child_it(ObjectType_& o, std::forward_iterator_tag /*unused*/)
|
||||||
|
{
|
||||||
|
return o.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ObjectType_>
|
||||||
|
static typename ObjectType_::iterator walk_child_it(ObjectType_& o)
|
||||||
|
{
|
||||||
|
JSON_ASSERT(!o.empty());
|
||||||
|
return walk_child_it(o, typename std::iterator_traits<typename ObjectType_::iterator>::iterator_category());
|
||||||
|
}
|
||||||
|
|
||||||
|
// the child of a non-empty array/object v that the walk in
|
||||||
|
// destroy_container() continues with (see walk_child_it() above)
|
||||||
|
static basic_json& walk_child(basic_json& v)
|
||||||
|
{
|
||||||
|
if (v.m_data.m_type == value_t::array)
|
||||||
|
{
|
||||||
|
return v.m_data.m_value.array->back();
|
||||||
|
}
|
||||||
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
|
return walk_child_it(*v.m_data.m_value.object)->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// removes walk_child(v) from a non-empty array/object v; this never
|
||||||
|
// allocates, and since it is only ever called when that child is a
|
||||||
|
// scalar or an already-empty array/object, destroying it never
|
||||||
|
// recurses more than one level deep (see destroy() below)
|
||||||
|
static void pop_walk_child(basic_json& v)
|
||||||
|
{
|
||||||
|
if (v.m_data.m_type == value_t::array)
|
||||||
|
{
|
||||||
|
v.m_data.m_value.array->pop_back();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
|
v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deallocates the (already empty) array/object held by v; this is
|
||||||
|
// the same allocator-based free the old recursive implementation
|
||||||
|
// used, just factored out so every level of the walk in destroy()
|
||||||
|
// can share it
|
||||||
|
static void free_container(basic_json& v) noexcept
|
||||||
|
{
|
||||||
|
if (v.m_data.m_type == value_t::array)
|
||||||
|
{
|
||||||
|
JSON_ASSERT(v.m_data.m_value.array->empty());
|
||||||
|
AllocatorType<array_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
|
JSON_ASSERT(v.m_data.m_value.object->empty());
|
||||||
|
AllocatorType<object_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
||||||
|
}
|
||||||
|
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void destroy_string() noexcept
|
||||||
|
{
|
||||||
|
if (string == nullptr)
|
||||||
|
{
|
||||||
|
// not initialized (e.g., due to exception in the ctor)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AllocatorType<string_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_binary() noexcept
|
||||||
|
{
|
||||||
|
if (binary == nullptr)
|
||||||
|
{
|
||||||
|
// not initialized (e.g., due to exception in the ctor)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AllocatorType<binary_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// t must be value_t::array or value_t::object
|
||||||
|
void destroy_container(value_t t) noexcept
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
(t == value_t::object && object == nullptr) ||
|
(t == value_t::object && object == nullptr) ||
|
||||||
(t == value_t::array && array == nullptr) ||
|
(t == value_t::array && array == nullptr)
|
||||||
(t == value_t::string && string == nullptr) ||
|
|
||||||
(t == value_t::binary && binary == nullptr)
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// not initialized (e.g., due to exception in the ctor)
|
// not initialized (e.g., due to exception in the ctor)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (t == value_t::array || t == value_t::object)
|
|
||||||
|
// Destroy the tree without recursing per nesting level and
|
||||||
|
// without any heap allocation: a heap-allocated flattening
|
||||||
|
// stack (the previous implementation) can itself throw
|
||||||
|
// bad_alloc, which would escape this noexcept destructor and
|
||||||
|
// terminate the program (#5135).
|
||||||
|
//
|
||||||
|
// Instead, walk down a chain of children (always the one
|
||||||
|
// walk_child() picks), reversing links as we go: cur is the
|
||||||
|
// container currently being emptied, and prev is its parent
|
||||||
|
// (value_t::null when there is none). Each parent's
|
||||||
|
// walk_child() slot doubles as storage for that parent's own
|
||||||
|
// parent link while we are below it, so no extra memory is
|
||||||
|
// needed; the parent is not modified meanwhile, so
|
||||||
|
// walk_child() finds that same slot again on the way up. We
|
||||||
|
// only ever remove a child once it is a scalar or an empty
|
||||||
|
// array/object, which neither allocates nor recurses more
|
||||||
|
// than one level deep.
|
||||||
|
//
|
||||||
|
// This json_value is not itself a basic_json, so the
|
||||||
|
// top-level container is first moved into a local stand-in
|
||||||
|
// ("cur"); a default-constructed basic_json has a null
|
||||||
|
// pointer in its m_value (see data::m_value's initializer),
|
||||||
|
// so swapping it with *this leaves this union's own pointer
|
||||||
|
// null, and it is never looked at or freed a second time.
|
||||||
|
basic_json cur;
|
||||||
|
cur.m_data.m_type = t;
|
||||||
|
using std::swap;
|
||||||
|
swap(cur.m_data.m_value, *this);
|
||||||
|
|
||||||
|
basic_json prev; // value_t::null: no parent
|
||||||
|
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
// flatten the current json_value to a heap-allocated stack
|
if (has_no_children(cur))
|
||||||
std::vector<basic_json> stack;
|
|
||||||
|
|
||||||
// move the top-level items to stack
|
|
||||||
if (t == value_t::array)
|
|
||||||
{
|
{
|
||||||
stack.reserve(array->size());
|
if (prev.m_data.m_type == value_t::null)
|
||||||
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
stack.reserve(object->size());
|
|
||||||
for (auto&& it : *object)
|
|
||||||
{
|
{
|
||||||
stack.push_back(std::move(it.second));
|
free_container(cur);
|
||||||
}
|
return; // back at the top with nothing left to do
|
||||||
}
|
|
||||||
|
|
||||||
while (!stack.empty())
|
|
||||||
{
|
|
||||||
// move the last item to a local variable to be processed
|
|
||||||
basic_json current_item(std::move(stack.back()));
|
|
||||||
stack.pop_back();
|
|
||||||
|
|
||||||
// if current_item is array/object, move
|
|
||||||
// its children to the stack to be processed later
|
|
||||||
if (current_item.is_array())
|
|
||||||
{
|
|
||||||
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));
|
|
||||||
|
|
||||||
current_item.m_data.m_value.array->clear();
|
|
||||||
}
|
|
||||||
else if (current_item.is_object())
|
|
||||||
{
|
|
||||||
for (auto&& it : *current_item.m_data.m_value.object)
|
|
||||||
{
|
|
||||||
stack.push_back(std::move(it.second));
|
|
||||||
}
|
|
||||||
|
|
||||||
current_item.m_data.m_value.object->clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// it's now safe that current_item gets destructed
|
// ascend: detach the grandparent link from prev's
|
||||||
// since it doesn't have any children
|
// walk_child() slot, drop that (now null) slot, free cur
|
||||||
|
// (it is empty), then move up one level
|
||||||
|
basic_json gp;
|
||||||
|
take(gp, walk_child(prev));
|
||||||
|
pop_walk_child(prev);
|
||||||
|
|
||||||
|
free_container(cur);
|
||||||
|
|
||||||
|
take(cur, prev);
|
||||||
|
take(prev, gp);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
basic_json& cur_child_ref = walk_child(cur);
|
||||||
|
|
||||||
|
if (has_no_children(cur_child_ref))
|
||||||
|
{
|
||||||
|
// scalar, or already-empty array/object
|
||||||
|
pop_walk_child(cur);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// descend into the non-empty child, reversing the
|
||||||
|
// link: its slot takes over prev, and the child becomes
|
||||||
|
// the new cur
|
||||||
|
basic_json tmp;
|
||||||
|
take(tmp, cur_child_ref);
|
||||||
|
take(cur_child_ref, prev);
|
||||||
|
take(prev, cur);
|
||||||
|
take(cur, tmp);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy(value_t t)
|
||||||
|
{
|
||||||
switch (t)
|
switch (t)
|
||||||
{
|
{
|
||||||
case value_t::object:
|
|
||||||
{
|
|
||||||
AllocatorType<object_t> alloc;
|
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
{
|
|
||||||
AllocatorType<array_t> alloc;
|
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
destroy_string();
|
||||||
AllocatorType<string_t> alloc;
|
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
{
|
destroy_binary();
|
||||||
AllocatorType<binary_t> alloc;
|
break;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
case value_t::object:
|
||||||
|
case value_t::array:
|
||||||
|
destroy_container(t);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
@@ -714,9 +925,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
-22572
File diff suppressed because it is too large.
Load diff
@@ -7636,8 +7636,10 @@ std::size_t hash_iteratively(const BasicJsonType& j);
|
|||||||
@brief hash a JSON value
|
@brief hash a JSON value
|
||||||
|
|
||||||
The hash function tries to rely on std::hash where possible. Furthermore, the
|
The hash function tries to rely on std::hash where possible. Furthermore, the
|
||||||
type of the JSON value is taken into account to have different hash values for
|
type of the JSON value is taken into account, so null, false, and numbers may
|
||||||
null, 0, 0U, and false, etc.
|
hash differently from each other, but any two numbers that compare equal
|
||||||
|
under operator== hash equally regardless of which of number_integer,
|
||||||
|
number_unsigned, or number_float actually holds the value.
|
||||||
|
|
||||||
Hashing an array or an object hashes its elements, which used to call this
|
Hashing an array or an object hashes its elements, which used to call this
|
||||||
function again once per nesting level, so a value nested deeply enough
|
function again once per nesting level, so a value nested deeply enough
|
||||||
@@ -7656,8 +7658,6 @@ template<typename BasicJsonType>
|
|||||||
std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
|
std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
|
||||||
{
|
{
|
||||||
using string_t = typename BasicJsonType::string_t;
|
using string_t = typename BasicJsonType::string_t;
|
||||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
|
||||||
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
|
||||||
using number_float_t = typename BasicJsonType::number_float_t;
|
using number_float_t = typename BasicJsonType::number_float_t;
|
||||||
|
|
||||||
const auto type = static_cast<std::size_t>(j.type());
|
const auto type = static_cast<std::size_t>(j.type());
|
||||||
@@ -7714,21 +7714,24 @@ std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case BasicJsonType::value_t::number_integer:
|
case BasicJsonType::value_t::number_integer:
|
||||||
{
|
|
||||||
const auto h = std::hash<number_integer_t> {}(j.template get<number_integer_t>());
|
|
||||||
return combine(type, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
case BasicJsonType::value_t::number_unsigned:
|
case BasicJsonType::value_t::number_unsigned:
|
||||||
{
|
|
||||||
const auto h = std::hash<number_unsigned_t> {}(j.template get<number_unsigned_t>());
|
|
||||||
return combine(type, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
case BasicJsonType::value_t::number_float:
|
case BasicJsonType::value_t::number_float:
|
||||||
{
|
{
|
||||||
const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());
|
// operator== compares numbers by their mathematical value across
|
||||||
return combine(type, h);
|
// number_integer, number_unsigned, and number_float, so equal
|
||||||
|
// numbers of different internal types (0, 0U, 0.0) must hash the
|
||||||
|
// same. Two equal numbers have the same value, which converts to
|
||||||
|
// the same number_float_t, so all numbers share one type tag and
|
||||||
|
// hash that converted value. Adding zero turns -0.0 (equal to 0)
|
||||||
|
// into 0.0, as std::hash need not map both to the same hash.
|
||||||
|
// The converse does not hold: converting a number_float_t value
|
||||||
|
// to an integer type is lossy, so the result can hash
|
||||||
|
// differently, and unequal numbers that convert to the same
|
||||||
|
// number_float_t (e.g., 2^53 and 2^53 + 1) share a hash.
|
||||||
|
const auto number_type = static_cast<std::size_t>(BasicJsonType::value_t::number_float);
|
||||||
|
const auto value = j.template get<number_float_t>() + static_cast<number_float_t>(0);
|
||||||
|
const auto h = std::hash<number_float_t> {}(value);
|
||||||
|
return combine(number_type, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
case BasicJsonType::value_t::binary:
|
case BasicJsonType::value_t::binary:
|
||||||
@@ -12313,6 +12316,39 @@ scan_number_done:
|
|||||||
// read the next character and ignore whitespace
|
// read the next character and ignore whitespace
|
||||||
skip_whitespace();
|
skip_whitespace();
|
||||||
|
|
||||||
|
return scan_after_whitespace();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief scan the next token when the caller expects a separator (':' or
|
||||||
|
',') most of the time
|
||||||
|
|
||||||
|
After an object key the next token is almost always ':', after a value
|
||||||
|
inside an object or array almost always ','. Testing for that character
|
||||||
|
first is a compare and a well-predicted branch, where the switch in
|
||||||
|
scan_after_whitespace() is an indirect jump through a table. Anything else
|
||||||
|
goes through the switch, so the result is the same as scan()'s.
|
||||||
|
|
||||||
|
May only be called after scan() has run once (the BOM check is skipped).
|
||||||
|
*/
|
||||||
|
token_type scan_expecting(token_type expected_type)
|
||||||
|
{
|
||||||
|
JSON_ASSERT(expected_type == token_type::name_separator || expected_type == token_type::value_separator);
|
||||||
|
JSON_ASSERT(position.chars_read_total > 0);
|
||||||
|
const char_int_type expected_char = static_cast<unsigned char>((expected_type == token_type::name_separator) ? ':' : ',');
|
||||||
|
skip_whitespace();
|
||||||
|
if (JSON_HEDLEY_LIKELY(current == expected_char))
|
||||||
|
{
|
||||||
|
return expected_type;
|
||||||
|
}
|
||||||
|
return scan_after_whitespace();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// the part of scan() after the leading whitespace: skip comments and
|
||||||
|
/// scan the token that starts with current
|
||||||
|
token_type scan_after_whitespace()
|
||||||
|
{
|
||||||
// ignore comments
|
// ignore comments
|
||||||
while (ignore_comments && current == '/')
|
while (ignore_comments && current == '/')
|
||||||
{
|
{
|
||||||
@@ -12392,7 +12428,6 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
/// input adapter
|
/// input adapter
|
||||||
InputAdapterType ia;
|
InputAdapterType ia;
|
||||||
|
|
||||||
@@ -16966,6 +17001,12 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// the lexer would stop at a NUL and accept the digits before it
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(current == '\0'))
|
||||||
|
{
|
||||||
|
return sax->parse_error(chars_read, "00", parse_error::create(115, chars_read,
|
||||||
|
exception_message("invalid number text; last byte: 0x00", "high-precision number"), nullptr));
|
||||||
|
}
|
||||||
number_vector.push_back(static_cast<char>(current));
|
number_vector.push_back(static_cast<char>(current));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18422,7 +18463,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -18585,7 +18626,7 @@ class parser
|
|||||||
{
|
{
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of array (ignore_trailing_commas = true)
|
// or end of array (ignore_trailing_commas = true)
|
||||||
if (get_token() == token_type::value_separator)
|
if (get_token_expecting(token_type::value_separator))
|
||||||
{
|
{
|
||||||
// parse a new value
|
// parse a new value
|
||||||
get_token();
|
get_token();
|
||||||
@@ -18625,7 +18666,7 @@ class parser
|
|||||||
|
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of object (ignore_trailing_commas = true)
|
// or end of object (ignore_trailing_commas = true)
|
||||||
if (get_token() == token_type::value_separator)
|
if (get_token_expecting(token_type::value_separator))
|
||||||
{
|
{
|
||||||
get_token();
|
get_token();
|
||||||
|
|
||||||
@@ -18646,7 +18687,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -18690,6 +18731,13 @@ class parser
|
|||||||
return last_token = m_lexer.scan();
|
return last_token = m_lexer.scan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// get next token from lexer; true if it is the separator @a expected_type
|
||||||
|
/// (name_separator or value_separator), which it usually is
|
||||||
|
bool get_token_expecting(token_type expected_type)
|
||||||
|
{
|
||||||
|
return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type;
|
||||||
|
}
|
||||||
|
|
||||||
std::string exception_message(const token_type expected, const std::string& context)
|
std::string exception_message(const token_type expected, const std::string& context)
|
||||||
{
|
{
|
||||||
std::string error_msg = "syntax error ";
|
std::string error_msg = "syntax error ";
|
||||||
@@ -21587,6 +21635,7 @@ class binary_writer
|
|||||||
@throw type_error.316 if a string value or an object key is not valid
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
@throw type_error.317 if @a j is not an object
|
@throw type_error.317 if @a j is not an object
|
||||||
|
@throw type_error.321 if a value nested in @a j is discarded
|
||||||
*/
|
*/
|
||||||
void write_bson(const BasicJsonType& j)
|
void write_bson(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -21618,6 +21667,7 @@ class binary_writer
|
|||||||
@param[in] j JSON value to serialize
|
@param[in] j JSON value to serialize
|
||||||
@throw type_error.316 if a string value or an object key is not valid
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
|
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||||
*/
|
*/
|
||||||
void write_cbor(const BasicJsonType& j)
|
void write_cbor(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -21782,7 +21832,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, "CBOR");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21842,6 +21892,7 @@ class binary_writer
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] j JSON value to serialize
|
@param[in] j JSON value to serialize
|
||||||
|
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||||
*/
|
*/
|
||||||
void write_msgpack(const BasicJsonType& j)
|
void write_msgpack(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -22115,7 +22166,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, "MessagePack");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22128,6 +22179,7 @@ class binary_writer
|
|||||||
@param[in] bjdata_version which BJData version to use, default is draft2
|
@param[in] bjdata_version which BJData version to use, default is draft2
|
||||||
@throw type_error.316 if a string value or an object key is not valid
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
|
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||||
*/
|
*/
|
||||||
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||||
const bool use_type, const bool add_prefix = true,
|
const bool use_type, const bool add_prefix = true,
|
||||||
@@ -22361,7 +22413,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22381,6 +22433,15 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/*!
|
||||||
|
@brief throws because @a j is discarded and cannot be serialized
|
||||||
|
@throw type_error.321 always
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
|
||||||
|
{
|
||||||
|
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
|
||||||
|
}
|
||||||
|
|
||||||
//////////
|
//////////
|
||||||
// BSON //
|
// BSON //
|
||||||
//////////
|
//////////
|
||||||
@@ -22632,6 +22693,7 @@ class binary_writer
|
|||||||
into a byte, before anything is written
|
into a byte, before anything is written
|
||||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||||
anything is written
|
anything is written
|
||||||
|
@throw type_error.321 if @a j is discarded
|
||||||
*/
|
*/
|
||||||
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -22658,10 +22720,12 @@ class binary_writer
|
|||||||
case value_t::null:
|
case value_t::null:
|
||||||
return 0ul;
|
return 0ul;
|
||||||
|
|
||||||
|
case value_t::discarded:
|
||||||
|
throw_on_discarded(j, "BSON");
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
return 0ul;
|
return 0ul;
|
||||||
@@ -22698,10 +22762,12 @@ class binary_writer
|
|||||||
case value_t::null:
|
case value_t::null:
|
||||||
return write_bson_null(name);
|
return write_bson_null(name);
|
||||||
|
|
||||||
|
case value_t::discarded:
|
||||||
|
throw_on_discarded(j, "BSON");
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
return;
|
return;
|
||||||
@@ -22768,6 +22834,8 @@ class binary_writer
|
|||||||
byte, before anything is written
|
byte, before anything is written
|
||||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||||
before anything is written
|
before anything is written
|
||||||
|
@throw type_error.321 if a value nested in @a document is discarded,
|
||||||
|
before anything is written
|
||||||
*/
|
*/
|
||||||
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
||||||
{
|
{
|
||||||
@@ -27406,6 +27474,70 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// SAX interface type, see @ref nlohmann::json_sax
|
/// SAX interface type, see @ref nlohmann::json_sax
|
||||||
using json_sax_t = json_sax<basic_json>;
|
using json_sax_t = json_sax<basic_json>;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// utility templates to create a json type with different template parameters //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Json type using a different type for storing objects
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<template<typename, typename, typename...> class ObjectType2>
|
||||||
|
using with_object_t = basic_json<ObjectType2, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type for storing arrays
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<template<typename, typename...> class ArrayType2>
|
||||||
|
using with_array_t = basic_json<ObjectType, ArrayType2, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type for storing strings
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<class StringType2>
|
||||||
|
using with_string_t = basic_json<ObjectType, ArrayType, StringType2, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type for storing booleans
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<class BooleanType2>
|
||||||
|
using with_boolean_t = basic_json<ObjectType, ArrayType, StringType, BooleanType2,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using different types for storing signed and unsigned integers
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<class NumberIntegerType2, class NumberUnsignedType2>
|
||||||
|
using with_integers_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType2, NumberUnsignedType2, NumberFloatType, AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type for storing floating point numbers
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<class NumberFloatType2>
|
||||||
|
using with_float_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType2, AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type as base allocator
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<template<typename> class AllocatorType2>
|
||||||
|
using with_allocator_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType2, JSONSerializer, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type as json serializer
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<template<typename, typename = void> class JSONSerializer2>
|
||||||
|
using with_json_serializer_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer2, BinaryType, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type for storing binary data
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<class BinaryType2>
|
||||||
|
using with_binary_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType2, CustomBaseClass>;
|
||||||
|
|
||||||
|
/// Json type using a different type as base class
|
||||||
|
/// @sa https://json.nlohmann.me/api/basic_json/with_t/
|
||||||
|
template<class CustomBaseClass2>
|
||||||
|
using with_base_class_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
||||||
|
NumberIntegerType, NumberUnsignedType, NumberFloatType, AllocatorType, JSONSerializer, BinaryType, CustomBaseClass2>;
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// exceptions //
|
// exceptions //
|
||||||
////////////////
|
////////////////
|
||||||
@@ -27792,100 +27924,247 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// constructor for rvalue binary arrays (internal type)
|
/// constructor for rvalue binary arrays (internal type)
|
||||||
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
||||||
|
|
||||||
void destroy(value_t t)
|
private:
|
||||||
|
// raw, allocation-free transfer of m_data from src to dst: no
|
||||||
|
// set_parents()/assert_invariant() (the former is O(#children) per
|
||||||
|
// call under JSON_DIAGNOSTICS, which would make the walk below
|
||||||
|
// quadratic); dst takes ownership, src is left as value_t::null.
|
||||||
|
static void take(basic_json& dst, basic_json& src) noexcept
|
||||||
|
{
|
||||||
|
dst.m_data.m_type = src.m_data.m_type;
|
||||||
|
dst.m_data.m_value = src.m_data.m_value;
|
||||||
|
src.m_data.m_type = value_t::null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// true if v is not an array/object, or is an already-empty one
|
||||||
|
static bool has_no_children(const basic_json& v) noexcept
|
||||||
|
{
|
||||||
|
switch (v.m_data.m_type)
|
||||||
|
{
|
||||||
|
case value_t::array:
|
||||||
|
return v.m_data.m_value.array->empty();
|
||||||
|
case value_t::object:
|
||||||
|
return v.m_data.m_value.object->empty();
|
||||||
|
case value_t::null:
|
||||||
|
case value_t::string:
|
||||||
|
case value_t::boolean:
|
||||||
|
case value_t::number_integer:
|
||||||
|
case value_t::number_unsigned:
|
||||||
|
case value_t::number_float:
|
||||||
|
case value_t::binary:
|
||||||
|
case value_t::discarded:
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The walk in destroy_container() may take the children of a
|
||||||
|
// container in any order, as long as it picks the same child again
|
||||||
|
// while that container is not modified in between. Arrays and
|
||||||
|
// objects with bidirectional iterators (std::map, ordered_map, ...)
|
||||||
|
// use their last child, which a vector-based container can remove
|
||||||
|
// in O(1). ObjectType only needs forward iterators, though (e.g.
|
||||||
|
// std::unordered_map), so other objects use their first child.
|
||||||
|
template<typename ObjectType_>
|
||||||
|
static typename ObjectType_::iterator walk_child_it(ObjectType_& o, std::bidirectional_iterator_tag /*unused*/)
|
||||||
|
{
|
||||||
|
return std::prev(o.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ObjectType_>
|
||||||
|
static typename ObjectType_::iterator walk_child_it(ObjectType_& o, std::forward_iterator_tag /*unused*/)
|
||||||
|
{
|
||||||
|
return o.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ObjectType_>
|
||||||
|
static typename ObjectType_::iterator walk_child_it(ObjectType_& o)
|
||||||
|
{
|
||||||
|
JSON_ASSERT(!o.empty());
|
||||||
|
return walk_child_it(o, typename std::iterator_traits<typename ObjectType_::iterator>::iterator_category());
|
||||||
|
}
|
||||||
|
|
||||||
|
// the child of a non-empty array/object v that the walk in
|
||||||
|
// destroy_container() continues with (see walk_child_it() above)
|
||||||
|
static basic_json& walk_child(basic_json& v)
|
||||||
|
{
|
||||||
|
if (v.m_data.m_type == value_t::array)
|
||||||
|
{
|
||||||
|
return v.m_data.m_value.array->back();
|
||||||
|
}
|
||||||
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
|
return walk_child_it(*v.m_data.m_value.object)->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// removes walk_child(v) from a non-empty array/object v; this never
|
||||||
|
// allocates, and since it is only ever called when that child is a
|
||||||
|
// scalar or an already-empty array/object, destroying it never
|
||||||
|
// recurses more than one level deep (see destroy() below)
|
||||||
|
static void pop_walk_child(basic_json& v)
|
||||||
|
{
|
||||||
|
if (v.m_data.m_type == value_t::array)
|
||||||
|
{
|
||||||
|
v.m_data.m_value.array->pop_back();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
|
v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deallocates the (already empty) array/object held by v; this is
|
||||||
|
// the same allocator-based free the old recursive implementation
|
||||||
|
// used, just factored out so every level of the walk in destroy()
|
||||||
|
// can share it
|
||||||
|
static void free_container(basic_json& v) noexcept
|
||||||
|
{
|
||||||
|
if (v.m_data.m_type == value_t::array)
|
||||||
|
{
|
||||||
|
JSON_ASSERT(v.m_data.m_value.array->empty());
|
||||||
|
AllocatorType<array_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
|
JSON_ASSERT(v.m_data.m_value.object->empty());
|
||||||
|
AllocatorType<object_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
||||||
|
}
|
||||||
|
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void destroy_string() noexcept
|
||||||
|
{
|
||||||
|
if (string == nullptr)
|
||||||
|
{
|
||||||
|
// not initialized (e.g., due to exception in the ctor)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AllocatorType<string_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_binary() noexcept
|
||||||
|
{
|
||||||
|
if (binary == nullptr)
|
||||||
|
{
|
||||||
|
// not initialized (e.g., due to exception in the ctor)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AllocatorType<binary_t> alloc;
|
||||||
|
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||||
|
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// t must be value_t::array or value_t::object
|
||||||
|
void destroy_container(value_t t) noexcept
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
(t == value_t::object && object == nullptr) ||
|
(t == value_t::object && object == nullptr) ||
|
||||||
(t == value_t::array && array == nullptr) ||
|
(t == value_t::array && array == nullptr)
|
||||||
(t == value_t::string && string == nullptr) ||
|
|
||||||
(t == value_t::binary && binary == nullptr)
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// not initialized (e.g., due to exception in the ctor)
|
// not initialized (e.g., due to exception in the ctor)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (t == value_t::array || t == value_t::object)
|
|
||||||
|
// Destroy the tree without recursing per nesting level and
|
||||||
|
// without any heap allocation: a heap-allocated flattening
|
||||||
|
// stack (the previous implementation) can itself throw
|
||||||
|
// bad_alloc, which would escape this noexcept destructor and
|
||||||
|
// terminate the program (#5135).
|
||||||
|
//
|
||||||
|
// Instead, walk down a chain of children (always the one
|
||||||
|
// walk_child() picks), reversing links as we go: cur is the
|
||||||
|
// container currently being emptied, and prev is its parent
|
||||||
|
// (value_t::null when there is none). Each parent's
|
||||||
|
// walk_child() slot doubles as storage for that parent's own
|
||||||
|
// parent link while we are below it, so no extra memory is
|
||||||
|
// needed; the parent is not modified meanwhile, so
|
||||||
|
// walk_child() finds that same slot again on the way up. We
|
||||||
|
// only ever remove a child once it is a scalar or an empty
|
||||||
|
// array/object, which neither allocates nor recurses more
|
||||||
|
// than one level deep.
|
||||||
|
//
|
||||||
|
// This json_value is not itself a basic_json, so the
|
||||||
|
// top-level container is first moved into a local stand-in
|
||||||
|
// ("cur"); a default-constructed basic_json has a null
|
||||||
|
// pointer in its m_value (see data::m_value's initializer),
|
||||||
|
// so swapping it with *this leaves this union's own pointer
|
||||||
|
// null, and it is never looked at or freed a second time.
|
||||||
|
basic_json cur;
|
||||||
|
cur.m_data.m_type = t;
|
||||||
|
using std::swap;
|
||||||
|
swap(cur.m_data.m_value, *this);
|
||||||
|
|
||||||
|
basic_json prev; // value_t::null: no parent
|
||||||
|
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
// flatten the current json_value to a heap-allocated stack
|
if (has_no_children(cur))
|
||||||
std::vector<basic_json> stack;
|
|
||||||
|
|
||||||
// move the top-level items to stack
|
|
||||||
if (t == value_t::array)
|
|
||||||
{
|
{
|
||||||
stack.reserve(array->size());
|
if (prev.m_data.m_type == value_t::null)
|
||||||
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
stack.reserve(object->size());
|
|
||||||
for (auto&& it : *object)
|
|
||||||
{
|
{
|
||||||
stack.push_back(std::move(it.second));
|
free_container(cur);
|
||||||
}
|
return; // back at the top with nothing left to do
|
||||||
}
|
|
||||||
|
|
||||||
while (!stack.empty())
|
|
||||||
{
|
|
||||||
// move the last item to a local variable to be processed
|
|
||||||
basic_json current_item(std::move(stack.back()));
|
|
||||||
stack.pop_back();
|
|
||||||
|
|
||||||
// if current_item is array/object, move
|
|
||||||
// its children to the stack to be processed later
|
|
||||||
if (current_item.is_array())
|
|
||||||
{
|
|
||||||
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));
|
|
||||||
|
|
||||||
current_item.m_data.m_value.array->clear();
|
|
||||||
}
|
|
||||||
else if (current_item.is_object())
|
|
||||||
{
|
|
||||||
for (auto&& it : *current_item.m_data.m_value.object)
|
|
||||||
{
|
|
||||||
stack.push_back(std::move(it.second));
|
|
||||||
}
|
|
||||||
|
|
||||||
current_item.m_data.m_value.object->clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// it's now safe that current_item gets destructed
|
// ascend: detach the grandparent link from prev's
|
||||||
// since it doesn't have any children
|
// walk_child() slot, drop that (now null) slot, free cur
|
||||||
|
// (it is empty), then move up one level
|
||||||
|
basic_json gp;
|
||||||
|
take(gp, walk_child(prev));
|
||||||
|
pop_walk_child(prev);
|
||||||
|
|
||||||
|
free_container(cur);
|
||||||
|
|
||||||
|
take(cur, prev);
|
||||||
|
take(prev, gp);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
basic_json& cur_child_ref = walk_child(cur);
|
||||||
|
|
||||||
|
if (has_no_children(cur_child_ref))
|
||||||
|
{
|
||||||
|
// scalar, or already-empty array/object
|
||||||
|
pop_walk_child(cur);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// descend into the non-empty child, reversing the
|
||||||
|
// link: its slot takes over prev, and the child becomes
|
||||||
|
// the new cur
|
||||||
|
basic_json tmp;
|
||||||
|
take(tmp, cur_child_ref);
|
||||||
|
take(cur_child_ref, prev);
|
||||||
|
take(prev, cur);
|
||||||
|
take(cur, tmp);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy(value_t t)
|
||||||
|
{
|
||||||
switch (t)
|
switch (t)
|
||||||
{
|
{
|
||||||
case value_t::object:
|
|
||||||
{
|
|
||||||
AllocatorType<object_t> alloc;
|
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
{
|
|
||||||
AllocatorType<array_t> alloc;
|
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
destroy_string();
|
||||||
AllocatorType<string_t> alloc;
|
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
{
|
destroy_binary();
|
||||||
AllocatorType<binary_t> alloc;
|
break;
|
||||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
|
||||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
case value_t::object:
|
||||||
|
case value_t::array:
|
||||||
|
destroy_container(t);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
@@ -27894,9 +28173,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,14 +48,7 @@ TEST_CASE("bad_alloc")
|
|||||||
SECTION("bad_alloc")
|
SECTION("bad_alloc")
|
||||||
{
|
{
|
||||||
// create JSON type using the throwing allocator
|
// create JSON type using the throwing allocator
|
||||||
using bad_json = nlohmann::basic_json<std::map,
|
using bad_json = nlohmann::json::with_allocator_t<bad_allocator>;
|
||||||
std::vector,
|
|
||||||
std::string,
|
|
||||||
bool,
|
|
||||||
std::int64_t,
|
|
||||||
std::uint64_t,
|
|
||||||
double,
|
|
||||||
bad_allocator>;
|
|
||||||
|
|
||||||
// creating an object should throw
|
// creating an object should throw
|
||||||
CHECK_THROWS_AS(bad_json(bad_json::value_t::object), std::bad_alloc&);
|
CHECK_THROWS_AS(bad_json(bad_json::value_t::object), std::bad_alloc&);
|
||||||
@@ -129,14 +122,7 @@ void my_allocator_clean_up(T* p)
|
|||||||
TEST_CASE("controlled bad_alloc")
|
TEST_CASE("controlled bad_alloc")
|
||||||
{
|
{
|
||||||
// create JSON type using the throwing allocator
|
// create JSON type using the throwing allocator
|
||||||
using my_json = nlohmann::basic_json<std::map,
|
using my_json = nlohmann::json::with_allocator_t<my_allocator>;
|
||||||
std::vector,
|
|
||||||
std::string,
|
|
||||||
bool,
|
|
||||||
std::int64_t,
|
|
||||||
std::uint64_t,
|
|
||||||
double,
|
|
||||||
my_allocator>;
|
|
||||||
|
|
||||||
SECTION("class json_value")
|
SECTION("class json_value")
|
||||||
{
|
{
|
||||||
@@ -593,17 +579,95 @@ TEST_CASE("bad my_allocator::construct")
|
|||||||
{
|
{
|
||||||
SECTION("my_allocator::construct doesn't forward")
|
SECTION("my_allocator::construct doesn't forward")
|
||||||
{
|
{
|
||||||
using bad_alloc_json = nlohmann::basic_json<std::map,
|
using bad_alloc_json = nlohmann::json::with_allocator_t<allocator_no_forward>;
|
||||||
std::vector,
|
|
||||||
std::string,
|
|
||||||
bool,
|
|
||||||
std::int64_t,
|
|
||||||
std::uint64_t,
|
|
||||||
double,
|
|
||||||
allocator_no_forward>;
|
|
||||||
|
|
||||||
bad_alloc_json j;
|
bad_alloc_json j;
|
||||||
j["test"] = bad_alloc_json::array_t();
|
j["test"] = bad_alloc_json::array_t();
|
||||||
j["test"].push_back("should not leak");
|
j["test"].push_back("should not leak");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::size_t counting_allocator_allocations = 0;
|
||||||
|
std::size_t counting_allocator_deallocations = 0;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct counting_allocator : std::allocator<T>
|
||||||
|
{
|
||||||
|
using std::allocator<T>::allocator;
|
||||||
|
|
||||||
|
T* allocate(std::size_t n)
|
||||||
|
{
|
||||||
|
++counting_allocator_allocations;
|
||||||
|
return std::allocator<T>::allocate(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(T* p, std::size_t n)
|
||||||
|
{
|
||||||
|
++counting_allocator_deallocations;
|
||||||
|
std::allocator<T>::deallocate(p, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class U>
|
||||||
|
struct rebind
|
||||||
|
{
|
||||||
|
using other = counting_allocator<U>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("destructor performs no allocation, only deallocation")
|
||||||
|
{
|
||||||
|
// see https://github.com/nlohmann/json/issues/4842 and
|
||||||
|
// https://github.com/nlohmann/json/issues/5135: destroying nested
|
||||||
|
// arrays/objects used to allocate a temporary stack (first with
|
||||||
|
// std::allocator, later - after #4842 - with the provided allocator).
|
||||||
|
// Since that stack could itself throw bad_alloc from inside the
|
||||||
|
// noexcept destructor (#5135), destroy() no longer allocates anything:
|
||||||
|
// it only ever frees what is already there.
|
||||||
|
using counting_json = nlohmann::basic_json<std::map,
|
||||||
|
std::vector,
|
||||||
|
std::string,
|
||||||
|
bool,
|
||||||
|
std::int64_t,
|
||||||
|
std::uint64_t,
|
||||||
|
double,
|
||||||
|
counting_allocator>;
|
||||||
|
|
||||||
|
SECTION("array")
|
||||||
|
{
|
||||||
|
auto* j = new counting_json({1, {2, {3, 4}}, 5}); // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
|
const auto allocations_before = counting_allocator_allocations;
|
||||||
|
const auto deallocations_before = counting_allocator_deallocations;
|
||||||
|
delete j; // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
|
CHECK(counting_allocator_allocations == allocations_before);
|
||||||
|
CHECK(counting_allocator_deallocations > deallocations_before);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("object")
|
||||||
|
{
|
||||||
|
auto* j = new counting_json({{"a", {{"b", {1, 2}}}}, {"c", 3}}); // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
|
const auto allocations_before = counting_allocator_allocations;
|
||||||
|
const auto deallocations_before = counting_allocator_deallocations;
|
||||||
|
delete j; // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
|
CHECK(counting_allocator_allocations == allocations_before);
|
||||||
|
CHECK(counting_allocator_deallocations > deallocations_before);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("mixed tree of empty/non-empty arrays and objects")
|
||||||
|
{
|
||||||
|
auto* j = new counting_json( // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
|
{
|
||||||
|
{"empty_obj", counting_json::object()},
|
||||||
|
{"empty_arr", counting_json::array()},
|
||||||
|
{"nested", {{"a", counting_json::array({1, 2, counting_json::object()})}, {"b", 3}}},
|
||||||
|
{"tail", counting_json::array({counting_json::array({1}), 2, counting_json::array({3})})}
|
||||||
|
});
|
||||||
|
const auto allocations_before = counting_allocator_allocations;
|
||||||
|
const auto deallocations_before = counting_allocator_deallocations;
|
||||||
|
delete j; // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
|
CHECK(counting_allocator_allocations == allocations_before);
|
||||||
|
CHECK(counting_allocator_deallocations > deallocations_before);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -163,16 +163,7 @@ void int_to_string(alt_string& target, std::size_t value)
|
|||||||
target = std::to_string(value).c_str();
|
target = std::to_string(value).c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
using alt_json = nlohmann::basic_json <
|
using alt_json = nlohmann::json::with_string_t<alt_string>;
|
||||||
std::map,
|
|
||||||
std::vector,
|
|
||||||
alt_string,
|
|
||||||
bool,
|
|
||||||
std::int64_t,
|
|
||||||
std::uint64_t,
|
|
||||||
double,
|
|
||||||
std::allocator,
|
|
||||||
nlohmann::adl_serializer >;
|
|
||||||
|
|
||||||
bool operator<(const char* op1, const alt_string& op2) noexcept
|
bool operator<(const char* op1, const alt_string& op2) noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -114,10 +114,59 @@ TEST_CASE("BJData")
|
|||||||
{
|
{
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// discarded values are not serialized
|
// a discarded value cannot be serialized to BJData
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
const auto result = json::to_bjdata(j);
|
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||||
CHECK(result.empty());
|
}
|
||||||
|
|
||||||
|
SECTION("discarded values nested in a container")
|
||||||
|
{
|
||||||
|
json const discarded = json::value_t::discarded;
|
||||||
|
|
||||||
|
SECTION("in an array")
|
||||||
|
{
|
||||||
|
json const j = {1, discarded, 2};
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to BJData", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("as an object value")
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
j["a"] = 1;
|
||||||
|
j["b"] = discarded;
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to BJData", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("nested deeper (array in object in array)")
|
||||||
|
{
|
||||||
|
json inner_array = {1, discarded};
|
||||||
|
json middle_object;
|
||||||
|
middle_object["x"] = inner_array;
|
||||||
|
json const j = {middle_object};
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to BJData", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("optimized array of all-discarded elements")
|
||||||
|
{
|
||||||
|
json const j = {discarded, discarded};
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to BJData", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("null")
|
SECTION("null")
|
||||||
@@ -1237,6 +1286,32 @@ TEST_CASE("BJData")
|
|||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec2), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1A", json::parse_error);
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec2), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1A", json::parse_error);
|
||||||
std::vector<uint8_t> const vec3 = {'H', 'i', 2, '1', '.'};
|
std::vector<uint8_t> const vec3 = {'H', 'i', 2, '1', '.'};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec3), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1.", json::parse_error);
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec3), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1.", json::parse_error);
|
||||||
|
// Reject NULs where they are read, including trailing NULs and payloads cut off after one.
|
||||||
|
SECTION("NUL in high-precision number (issue #5753)")
|
||||||
|
{
|
||||||
|
for (const auto& vec : std::vector<std::vector<uint8_t>>
|
||||||
|
{
|
||||||
|
{'H', 'i', 3, '1', 0, 'x'},
|
||||||
|
{'H', 'i', 2, '1', 0},
|
||||||
|
{'H', 'i', 3, '1', 0}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(vec)
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text; last byte: 0x00", json::parse_error);
|
||||||
|
CHECK(json::from_bjdata(vec, true, false).is_discarded());
|
||||||
|
CHECK(json::from_bjdata(vec, false, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> const nested = {'[', 'H', 'i', 3, '1', 0, 'x', ']'};
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(nested), "[json.exception.parse_error.115] parse error at byte 6: syntax error while parsing BJData high-precision number: invalid number text; last byte: 0x00", json::parse_error);
|
||||||
|
CHECK(json::from_bjdata(nested, true, false).is_discarded());
|
||||||
|
|
||||||
|
std::vector<uint8_t> const valid = {'H', 'i', 1, '1'};
|
||||||
|
const auto j = json::from_bjdata(valid);
|
||||||
|
CHECK(j.is_number_unsigned());
|
||||||
|
CHECK(j == json(1));
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> const vec_overflow = {'H', 'i', 5, '1', 'e', '4', '0', '0'};
|
std::vector<uint8_t> const vec_overflow = {'H', 'i', 5, '1', 'e', '4', '0', '0'};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec_overflow), "[json.exception.out_of_range.406] number overflow parsing '1e400'", json::out_of_range);
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec_overflow), "[json.exception.out_of_range.406] number overflow parsing '1e400'", json::out_of_range);
|
||||||
std::vector<uint8_t> const vec4 = {'H', 2, '1', '0'};
|
std::vector<uint8_t> const vec4 = {'H', 2, '1', '0'};
|
||||||
|
|||||||
@@ -149,6 +149,54 @@ TEST_CASE("BSON")
|
|||||||
json const j = std::vector<int> {1, 2, 3, 4, 5, 6, 7};
|
json const j = std::vector<int> {1, 2, 3, 4, 5, 6, 7};
|
||||||
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is array", json::type_error&);
|
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is array", json::type_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("discarded")
|
||||||
|
{
|
||||||
|
json const j = json::value_t::discarded;
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.317] to serialize to BSON, top-level type must be object, but is discarded", json::type_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("discarded values nested in a container cannot be serialized to BSON")
|
||||||
|
{
|
||||||
|
json const discarded = json::value_t::discarded;
|
||||||
|
|
||||||
|
SECTION("as an object value")
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
j["a"] = 1;
|
||||||
|
j["b"] = discarded;
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to BSON", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("in an array that is an object value")
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
j["a"] = json::array({1, discarded, 2});
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/a/1) cannot serialize discarded value to BSON", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("nested deeper (array in object in object)")
|
||||||
|
{
|
||||||
|
json inner_array = {1, discarded};
|
||||||
|
json middle_object;
|
||||||
|
middle_object["x"] = inner_array;
|
||||||
|
json j;
|
||||||
|
j["outer"] = middle_object;
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] (/outer/x/1) cannot serialize discarded value to BSON", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bson(j), "[json.exception.type_error.321] cannot serialize discarded value to BSON", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("keys containing code-point U+0000 cannot be serialized to BSON")
|
SECTION("keys containing code-point U+0000 cannot be serialized to BSON")
|
||||||
|
|||||||
+42
-3
@@ -38,10 +38,49 @@ TEST_CASE("CBOR")
|
|||||||
{
|
{
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// discarded values are not serialized
|
// a discarded value cannot be serialized to CBOR
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
const auto result = json::to_cbor(j);
|
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||||
CHECK(result.empty());
|
}
|
||||||
|
|
||||||
|
SECTION("discarded values nested in a container")
|
||||||
|
{
|
||||||
|
json const discarded = json::value_t::discarded;
|
||||||
|
|
||||||
|
SECTION("in an array")
|
||||||
|
{
|
||||||
|
json const j = {1, discarded, 2};
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to CBOR", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("as an object value")
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
j["a"] = 1;
|
||||||
|
j["b"] = discarded;
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to CBOR", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("nested deeper (array in object in array)")
|
||||||
|
{
|
||||||
|
json inner_array = {1, discarded};
|
||||||
|
json middle_object;
|
||||||
|
middle_object["x"] = inner_array;
|
||||||
|
json const j = {middle_object};
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to CBOR", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_cbor(j), "[json.exception.type_error.321] cannot serialize discarded value to CBOR", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("NaN")
|
SECTION("NaN")
|
||||||
|
|||||||
@@ -2317,6 +2317,58 @@ TEST_CASE("parser class")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("comments before separators")
|
||||||
|
{
|
||||||
|
// The parser first checks for the expected ':' or ',' and only then
|
||||||
|
// falls back to the full token switch, which skips comments. A comment
|
||||||
|
// directly before a separator takes that fallback.
|
||||||
|
json _;
|
||||||
|
|
||||||
|
SECTION("ignored")
|
||||||
|
{
|
||||||
|
const std::vector<std::pair<std::string, json>> inputs =
|
||||||
|
{
|
||||||
|
{"{\"a\" /* c */ : 1}", {{"a", 1}}},
|
||||||
|
{"{\"a\" // c\n: 1}", {{"a", 1}}},
|
||||||
|
{R"({"a": 1, "b" /* c */ : 2})", {{"a", 1}, {"b", 2}}},
|
||||||
|
{R"({"a": 1 /* c */ , "b": 2})", {{"a", 1}, {"b", 2}}},
|
||||||
|
{"{\"a\": 1 // c\n, \"b\": 2}", {{"a", 1}, {"b", 2}}},
|
||||||
|
{"[1 /* c */ , 2]", {1, 2}},
|
||||||
|
{"[1 // c\n, 2]", {1, 2}},
|
||||||
|
{"{\"a\" /* c */ /* d */ : [1 // c\n , 2 /**/ ] /**/ , \"b\" : 3}", {{"a", {1, 2}}, {"b", 3}}}
|
||||||
|
};
|
||||||
|
for (const auto& input : inputs)
|
||||||
|
{
|
||||||
|
CAPTURE(input.first)
|
||||||
|
CHECK(json::parse(input.first, nullptr, true, true) == input.second);
|
||||||
|
CHECK(json::accept(input.first, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ignored, with trailing commas")
|
||||||
|
{
|
||||||
|
CHECK(json::parse(std::string("[1 /* c */ , ]"), nullptr, true, true, true) == json({1}));
|
||||||
|
CHECK(json::parse(std::string("{\"a\": 1 /* c */ , }"), nullptr, true, true, true) == json({{"a", 1}}));
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("[1 /* c */ , ]"), nullptr, true, true),
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 14: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal", json::parse_error);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\": 1 /* c */ , }"), nullptr, true, true),
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 19: syntax error while parsing object key - unexpected '}'; expected string literal", json::parse_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("not ignored")
|
||||||
|
{
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\" /* c */ : 1}")),
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 6: syntax error while parsing object separator - invalid literal; last read: '\"a\" /'; expected ':'", json::parse_error);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\": 1, \"b\" /* c */ : 2}")),
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 14: syntax error while parsing object separator - invalid literal; last read: '\"b\" /'; expected ':'", json::parse_error);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("{\"a\": 1 /* c */ , \"b\": 2}")),
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 9: syntax error while parsing object - invalid literal; last read: '1 /'; expected '}'", json::parse_error);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse(std::string("[1 /* c */ , 2]")),
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing array - invalid literal; last read: '1 /'; expected ']'", json::parse_error);
|
||||||
|
CHECK(!json::accept(std::string("[1 /* c */ , 2]")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
// Macro for all test cases for start_pos and end_pos
|
// Macro for all test cases for start_pos and end_pos
|
||||||
#define SETUP_TESTCASES() \
|
#define SETUP_TESTCASES() \
|
||||||
|
|||||||
@@ -38,20 +38,7 @@ class json_metadata
|
|||||||
};
|
};
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
using json_with_metadata =
|
using json_with_metadata = nlohmann::json::with_base_class_t<json_metadata<T>>;
|
||||||
nlohmann::basic_json <
|
|
||||||
std::map,
|
|
||||||
std::vector,
|
|
||||||
std::string,
|
|
||||||
bool,
|
|
||||||
std::int64_t,
|
|
||||||
std::uint64_t,
|
|
||||||
double,
|
|
||||||
std::allocator,
|
|
||||||
nlohmann::adl_serializer,
|
|
||||||
std::vector<std::uint8_t>,
|
|
||||||
json_metadata<T>
|
|
||||||
>;
|
|
||||||
|
|
||||||
TEST_CASE("JSON Node Metadata")
|
TEST_CASE("JSON Node Metadata")
|
||||||
{
|
{
|
||||||
@@ -268,19 +255,7 @@ class visitor_adaptor
|
|||||||
void do_visit(const Ptr& ptr, const Fnc& fnc) const;
|
void do_visit(const Ptr& ptr, const Fnc& fnc) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
using json_with_visitor_t = nlohmann::basic_json <
|
using json_with_visitor_t = nlohmann::json::with_base_class_t<visitor_adaptor>;
|
||||||
std::map,
|
|
||||||
std::vector,
|
|
||||||
std::string,
|
|
||||||
bool,
|
|
||||||
std::int64_t,
|
|
||||||
std::uint64_t,
|
|
||||||
double,
|
|
||||||
std::allocator,
|
|
||||||
nlohmann::adl_serializer,
|
|
||||||
std::vector<std::uint8_t>,
|
|
||||||
visitor_adaptor
|
|
||||||
>;
|
|
||||||
|
|
||||||
template <class Fnc>
|
template <class Fnc>
|
||||||
void visitor_adaptor::visit(const Fnc& fnc) const
|
void visitor_adaptor::visit(const Fnc& fnc) const
|
||||||
|
|||||||
@@ -10,7 +10,9 @@
|
|||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <iterator>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
@@ -196,6 +198,198 @@ struct void_erase_map : std::map<Key, T, Compare, Allocator>
|
|||||||
|
|
||||||
using void_erase_json = nlohmann::basic_json<void_erase_map>;
|
using void_erase_json = nlohmann::basic_json<void_erase_map>;
|
||||||
|
|
||||||
|
// wraps an iterator, but only offers the LegacyForwardIterator operations,
|
||||||
|
// like the iterators of std::unordered_map and other hash maps
|
||||||
|
template<class BaseIterator>
|
||||||
|
class forward_only_iterator
|
||||||
|
{
|
||||||
|
BaseIterator m_it{};
|
||||||
|
|
||||||
|
public:
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
using value_type = typename std::iterator_traits<BaseIterator>::value_type;
|
||||||
|
using difference_type = typename std::iterator_traits<BaseIterator>::difference_type;
|
||||||
|
using pointer = typename std::iterator_traits<BaseIterator>::pointer;
|
||||||
|
using reference = typename std::iterator_traits<BaseIterator>::reference;
|
||||||
|
|
||||||
|
forward_only_iterator() = default;
|
||||||
|
explicit forward_only_iterator(BaseIterator it) : m_it(it) {}
|
||||||
|
|
||||||
|
BaseIterator base() const
|
||||||
|
{
|
||||||
|
return m_it;
|
||||||
|
}
|
||||||
|
|
||||||
|
reference operator*() const
|
||||||
|
{
|
||||||
|
return *m_it;
|
||||||
|
}
|
||||||
|
pointer operator->() const
|
||||||
|
{
|
||||||
|
return &*m_it;
|
||||||
|
}
|
||||||
|
forward_only_iterator& operator++()
|
||||||
|
{
|
||||||
|
++m_it;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
forward_only_iterator operator++(int)
|
||||||
|
{
|
||||||
|
auto result = *this;
|
||||||
|
++m_it;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator==(const forward_only_iterator& lhs, const forward_only_iterator& rhs)
|
||||||
|
{
|
||||||
|
return lhs.m_it == rhs.m_it;
|
||||||
|
}
|
||||||
|
friend bool operator!=(const forward_only_iterator& lhs, const forward_only_iterator& rhs)
|
||||||
|
{
|
||||||
|
return lhs.m_it != rhs.m_it;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// An ObjectType whose iterators are forward-only, as those of hash maps are;
|
||||||
|
// it has no rbegin() and its iterators no operator--. A hash map is not used
|
||||||
|
// directly for the same reason as in no_key_compare_map above.
|
||||||
|
template<class Key, class T, class Compare, class Allocator>
|
||||||
|
class forward_only_map
|
||||||
|
{
|
||||||
|
using map_t = std::map<Key, T, Compare, Allocator>;
|
||||||
|
map_t data;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using key_type = typename map_t::key_type;
|
||||||
|
using mapped_type = typename map_t::mapped_type;
|
||||||
|
using value_type = typename map_t::value_type;
|
||||||
|
using size_type = typename map_t::size_type;
|
||||||
|
using allocator_type = typename map_t::allocator_type;
|
||||||
|
using iterator = forward_only_iterator<typename map_t::iterator>;
|
||||||
|
using const_iterator = forward_only_iterator<typename map_t::const_iterator>;
|
||||||
|
|
||||||
|
forward_only_map() noexcept(std::is_nothrow_default_constructible<map_t>::value) : data() {}
|
||||||
|
|
||||||
|
template<class InputIt>
|
||||||
|
forward_only_map(InputIt first, InputIt last) : data(first, last) {}
|
||||||
|
|
||||||
|
iterator begin() noexcept
|
||||||
|
{
|
||||||
|
return iterator(data.begin());
|
||||||
|
}
|
||||||
|
iterator end() noexcept
|
||||||
|
{
|
||||||
|
return iterator(data.end());
|
||||||
|
}
|
||||||
|
const_iterator begin() const noexcept
|
||||||
|
{
|
||||||
|
return const_iterator(data.begin());
|
||||||
|
}
|
||||||
|
const_iterator end() const noexcept
|
||||||
|
{
|
||||||
|
return const_iterator(data.end());
|
||||||
|
}
|
||||||
|
const_iterator cbegin() const noexcept
|
||||||
|
{
|
||||||
|
return const_iterator(data.cbegin());
|
||||||
|
}
|
||||||
|
const_iterator cend() const noexcept
|
||||||
|
{
|
||||||
|
return const_iterator(data.cend());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const noexcept
|
||||||
|
{
|
||||||
|
return data.empty();
|
||||||
|
}
|
||||||
|
size_type size() const noexcept
|
||||||
|
{
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
size_type max_size() const noexcept
|
||||||
|
{
|
||||||
|
return data.max_size();
|
||||||
|
}
|
||||||
|
void clear() noexcept
|
||||||
|
{
|
||||||
|
data.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator find(const key_type& key)
|
||||||
|
{
|
||||||
|
return iterator(data.find(key));
|
||||||
|
}
|
||||||
|
const_iterator find(const key_type& key) const
|
||||||
|
{
|
||||||
|
return const_iterator(data.find(key));
|
||||||
|
}
|
||||||
|
size_type count(const key_type& key) const
|
||||||
|
{
|
||||||
|
return data.count(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<iterator, bool> emplace(const key_type& key, const mapped_type& value)
|
||||||
|
{
|
||||||
|
const auto result = data.emplace(key, value);
|
||||||
|
return {iterator(result.first), result.second};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<iterator, bool> insert(const value_type& value)
|
||||||
|
{
|
||||||
|
const auto result = data.insert(value);
|
||||||
|
return {iterator(result.first), result.second};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class InputIt>
|
||||||
|
void insert(InputIt first, InputIt last)
|
||||||
|
{
|
||||||
|
data.insert(first, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
mapped_type& operator[](const key_type& key)
|
||||||
|
{
|
||||||
|
return data[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
mapped_type& at(const key_type& key)
|
||||||
|
{
|
||||||
|
return data.at(key);
|
||||||
|
}
|
||||||
|
const mapped_type& at(const key_type& key) const
|
||||||
|
{
|
||||||
|
return data.at(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator erase(iterator pos)
|
||||||
|
{
|
||||||
|
return iterator(data.erase(pos.base()));
|
||||||
|
}
|
||||||
|
iterator erase(iterator first, iterator last)
|
||||||
|
{
|
||||||
|
return iterator(data.erase(first.base(), last.base()));
|
||||||
|
}
|
||||||
|
size_type erase(const key_type& key)
|
||||||
|
{
|
||||||
|
return data.erase(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap(forward_only_map& other) noexcept(noexcept(data.swap(other.data)))
|
||||||
|
{
|
||||||
|
data.swap(other.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator==(const forward_only_map& lhs, const forward_only_map& rhs)
|
||||||
|
{
|
||||||
|
return lhs.data == rhs.data;
|
||||||
|
}
|
||||||
|
friend bool operator<(const forward_only_map& lhs, const forward_only_map& rhs)
|
||||||
|
{
|
||||||
|
return lhs.data < rhs.data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using forward_only_json = nlohmann::basic_json<forward_only_map>;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("object type whose erase() returns void")
|
TEST_CASE("object type whose erase() returns void")
|
||||||
@@ -322,3 +516,46 @@ TEST_CASE("object type without key_compare")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE("object type with forward-only iterators")
|
||||||
|
{
|
||||||
|
CHECK(std::is_same<std::iterator_traits<forward_only_json::object_t::iterator>::iterator_category,
|
||||||
|
std::forward_iterator_tag>::value);
|
||||||
|
|
||||||
|
SECTION("destroying nested objects and arrays")
|
||||||
|
{
|
||||||
|
forward_only_json j;
|
||||||
|
j["a"] = 1;
|
||||||
|
j["b"]["c"] = "x";
|
||||||
|
j["b"]["d"] = forward_only_json::array();
|
||||||
|
j["b"]["d"].push_back(forward_only_json::object());
|
||||||
|
j["b"]["d"].push_back(true);
|
||||||
|
j["b"]["e"]["f"]["g"] = nullptr;
|
||||||
|
j["h"] = forward_only_json::object();
|
||||||
|
j["i"]["j"] = 2;
|
||||||
|
|
||||||
|
CHECK(j.size() == 4);
|
||||||
|
CHECK(j["b"].size() == 3);
|
||||||
|
CHECK(j["b"]["d"].size() == 2);
|
||||||
|
CHECK(j["b"]["e"]["f"]["g"].is_null());
|
||||||
|
|
||||||
|
CHECK(j.erase("b") == 1);
|
||||||
|
CHECK(j.size() == 3);
|
||||||
|
j = 42;
|
||||||
|
CHECK(j == 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("destroying a deeply nested object")
|
||||||
|
{
|
||||||
|
constexpr std::size_t depth = 100000;
|
||||||
|
forward_only_json j;
|
||||||
|
forward_only_json* cur = &j;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
(*cur)["s"] = i;
|
||||||
|
cur = &(*cur)["o"];
|
||||||
|
}
|
||||||
|
CHECK(j["o"]["o"]["s"] == 2);
|
||||||
|
// destroyed at the end of scope without recursing per level
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
-8
@@ -12,8 +12,10 @@
|
|||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
using ordered_json = nlohmann::ordered_json;
|
using ordered_json = nlohmann::ordered_json;
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -91,6 +93,9 @@ TEST_CASE("hash<nlohmann::json>")
|
|||||||
// Collect hashes for different JSON values and make sure that they are distinct
|
// Collect hashes for different JSON values and make sure that they are distinct
|
||||||
// We cannot compare against fixed values, because the implementation of
|
// We cannot compare against fixed values, because the implementation of
|
||||||
// std::hash may differ between compilers.
|
// std::hash may differ between compilers.
|
||||||
|
//
|
||||||
|
// numbers that compare equal under operator== (0 == 0U == 0.0) must hash
|
||||||
|
// equally, so they are only inserted once below and checked separately.
|
||||||
|
|
||||||
std::set<std::size_t> hashes;
|
std::set<std::size_t> hashes;
|
||||||
|
|
||||||
@@ -107,10 +112,7 @@ TEST_CASE("hash<nlohmann::json>")
|
|||||||
|
|
||||||
// number
|
// number
|
||||||
hashes.insert(std::hash<json> {}(json(0)));
|
hashes.insert(std::hash<json> {}(json(0)));
|
||||||
hashes.insert(std::hash<json> {}(json(static_cast<unsigned>(0))));
|
|
||||||
|
|
||||||
hashes.insert(std::hash<json> {}(json(-1)));
|
hashes.insert(std::hash<json> {}(json(-1)));
|
||||||
hashes.insert(std::hash<json> {}(json(0.0)));
|
|
||||||
hashes.insert(std::hash<json> {}(json(42.23)));
|
hashes.insert(std::hash<json> {}(json(42.23)));
|
||||||
|
|
||||||
// array
|
// array
|
||||||
@@ -132,7 +134,36 @@ TEST_CASE("hash<nlohmann::json>")
|
|||||||
// discarded
|
// discarded
|
||||||
hashes.insert(std::hash<json> {}(json(json::value_t::discarded)));
|
hashes.insert(std::hash<json> {}(json(json::value_t::discarded)));
|
||||||
|
|
||||||
CHECK(hashes.size() == 21);
|
CHECK(hashes.size() == 19);
|
||||||
|
|
||||||
|
// numbers that compare equal under operator== must hash equally,
|
||||||
|
// regardless of which of number_integer, number_unsigned, or
|
||||||
|
// number_float actually holds the value
|
||||||
|
CHECK(json(0) == json(static_cast<unsigned>(0)));
|
||||||
|
CHECK(json(0) == json(0.0));
|
||||||
|
CHECK(std::hash<json> {}(json(0)) == std::hash<json> {}(json(static_cast<unsigned>(0))));
|
||||||
|
CHECK(std::hash<json> {}(json(0)) == std::hash<json> {}(json(0.0)));
|
||||||
|
CHECK(std::hash<json> {}(json(-1)) == std::hash<json> {}(json(-1.0)));
|
||||||
|
|
||||||
|
// a std::unordered_set relies on this same consistency between == and hash
|
||||||
|
const std::unordered_set<json> numbers {json(0), json(static_cast<unsigned>(0)), json(0.0)};
|
||||||
|
CHECK(numbers.size() == 1);
|
||||||
|
|
||||||
|
// -0.0 compares equal to 0 and 0.0
|
||||||
|
CHECK(json(-0.0) == json(0));
|
||||||
|
CHECK(std::hash<json> {}(json(-0.0)) == std::hash<json> {}(json(0)));
|
||||||
|
CHECK(std::hash<json> {}(json(-0.0)) == std::hash<json> {}(json(0.0)));
|
||||||
|
|
||||||
|
// the ends of the integer ranges, which equal floats exactly
|
||||||
|
const auto int_min = (std::numeric_limits<json::number_integer_t>::min)();
|
||||||
|
const auto int_max = (std::numeric_limits<json::number_integer_t>::max)();
|
||||||
|
const auto two_63 = json::number_unsigned_t(1) << 63U;
|
||||||
|
CHECK(json(int_min) == json(-9223372036854775808.0));
|
||||||
|
CHECK(std::hash<json> {}(json(int_min)) == std::hash<json> {}(json(-9223372036854775808.0)));
|
||||||
|
CHECK(json(two_63) == json(9223372036854775808.0));
|
||||||
|
CHECK(std::hash<json> {}(json(two_63)) == std::hash<json> {}(json(9223372036854775808.0)));
|
||||||
|
CHECK(json(json::number_unsigned_t(int_max)) == json(int_max));
|
||||||
|
CHECK(std::hash<json> {}(json(json::number_unsigned_t(int_max))) == std::hash<json> {}(json(int_max)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("hash<nlohmann::ordered_json>")
|
TEST_CASE("hash<nlohmann::ordered_json>")
|
||||||
@@ -156,10 +187,7 @@ TEST_CASE("hash<nlohmann::ordered_json>")
|
|||||||
|
|
||||||
// number
|
// number
|
||||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(0)));
|
hashes.insert(std::hash<ordered_json> {}(ordered_json(0)));
|
||||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(static_cast<unsigned>(0))));
|
|
||||||
|
|
||||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(-1)));
|
hashes.insert(std::hash<ordered_json> {}(ordered_json(-1)));
|
||||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(0.0)));
|
|
||||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(42.23)));
|
hashes.insert(std::hash<ordered_json> {}(ordered_json(42.23)));
|
||||||
|
|
||||||
// array
|
// array
|
||||||
@@ -181,7 +209,10 @@ TEST_CASE("hash<nlohmann::ordered_json>")
|
|||||||
// discarded
|
// discarded
|
||||||
hashes.insert(std::hash<ordered_json> {}(ordered_json(ordered_json::value_t::discarded)));
|
hashes.insert(std::hash<ordered_json> {}(ordered_json(ordered_json::value_t::discarded)));
|
||||||
|
|
||||||
CHECK(hashes.size() == 21);
|
CHECK(hashes.size() == 19);
|
||||||
|
|
||||||
|
CHECK(std::hash<ordered_json> {}(ordered_json(0)) == std::hash<ordered_json> {}(ordered_json(static_cast<unsigned>(0))));
|
||||||
|
CHECK(std::hash<ordered_json> {}(ordered_json(0)) == std::hash<ordered_json> {}(ordered_json(0.0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("hash of deeply nested values")
|
TEST_CASE("hash of deeply nested values")
|
||||||
|
|||||||
@@ -41,10 +41,49 @@ TEST_CASE("MessagePack")
|
|||||||
{
|
{
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// discarded values are not serialized
|
// a discarded value cannot be serialized to MessagePack
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
const auto result = json::to_msgpack(j);
|
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||||
CHECK(result.empty());
|
}
|
||||||
|
|
||||||
|
SECTION("discarded values nested in a container")
|
||||||
|
{
|
||||||
|
json const discarded = json::value_t::discarded;
|
||||||
|
|
||||||
|
SECTION("in an array")
|
||||||
|
{
|
||||||
|
json const j = {1, discarded, 2};
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to MessagePack", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("as an object value")
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
j["a"] = 1;
|
||||||
|
j["b"] = discarded;
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to MessagePack", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("nested deeper (array in object in array)")
|
||||||
|
{
|
||||||
|
json inner_array = {1, discarded};
|
||||||
|
json middle_object;
|
||||||
|
middle_object["x"] = inner_array;
|
||||||
|
json const j = {middle_object};
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to MessagePack", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_msgpack(j), "[json.exception.type_error.321] cannot serialize discarded value to MessagePack", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("null")
|
SECTION("null")
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ using ordered_json = nlohmann::ordered_json;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <new>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -107,6 +109,84 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
|||||||
|
|
||||||
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
||||||
|
|
||||||
|
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// An allocator whose allocate() can be told to fail on demand, so tests can
|
||||||
|
// check that ~basic_json() tolerates - in fact, after #5135, never even
|
||||||
|
// triggers - an allocation failure. This replaces an earlier version of
|
||||||
|
// this test that overrode the process-wide ::operator new/::operator
|
||||||
|
// delete, which affected every allocation in the whole unit-regression2
|
||||||
|
// binary rather than just the values under test.
|
||||||
|
std::size_t failing_allocator_allocations = 0;
|
||||||
|
std::size_t failing_allocator_deallocations = 0;
|
||||||
|
bool fail_next_allocation = false;
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct failing_allocator : std::allocator<T>
|
||||||
|
{
|
||||||
|
using std::allocator<T>::allocator;
|
||||||
|
|
||||||
|
failing_allocator() noexcept = default;
|
||||||
|
template<class U>
|
||||||
|
failing_allocator(const failing_allocator<U>& /*unused*/) noexcept {} // NOLINT(google-explicit-constructor)
|
||||||
|
|
||||||
|
T* allocate(std::size_t n)
|
||||||
|
{
|
||||||
|
if (fail_next_allocation)
|
||||||
|
{
|
||||||
|
fail_next_allocation = false;
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
++failing_allocator_allocations;
|
||||||
|
return std::allocator<T>::allocate(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(T* p, std::size_t n)
|
||||||
|
{
|
||||||
|
++failing_allocator_deallocations;
|
||||||
|
std::allocator<T>::deallocate(p, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class U>
|
||||||
|
struct rebind
|
||||||
|
{
|
||||||
|
using other = failing_allocator<U>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
using failing_json = nlohmann::basic_json<std::map, std::vector, std::string, bool,
|
||||||
|
std::int64_t, std::uint64_t, double, failing_allocator>;
|
||||||
|
using failing_ordered_json = nlohmann::basic_json<nlohmann::ordered_map, std::vector, std::string, bool,
|
||||||
|
std::int64_t, std::uint64_t, double, failing_allocator>;
|
||||||
|
|
||||||
|
// builds `depth` levels of nesting around a scalar, iteratively (never
|
||||||
|
// recursing: each wrap only moves the previous, already-built value, which
|
||||||
|
// is O(1)), each level an array or an object depending on `nest_objects`
|
||||||
|
template<class BasicJsonType>
|
||||||
|
BasicJsonType make_deep_nest(std::size_t depth, bool nest_objects)
|
||||||
|
{
|
||||||
|
BasicJsonType v = 0;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
if (nest_objects)
|
||||||
|
{
|
||||||
|
BasicJsonType wrapper = BasicJsonType::object();
|
||||||
|
wrapper["x"] = std::move(v);
|
||||||
|
v = std::move(wrapper);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BasicJsonType wrapper = BasicJsonType::array();
|
||||||
|
wrapper.push_back(std::move(v));
|
||||||
|
v = std::move(wrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
#endif
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
// for #1647
|
// for #1647
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
@@ -940,4 +1020,211 @@ TEST_CASE("regression test - excessive binary container size honors allow_except
|
|||||||
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9b, 0, 0, 0, 0, 0, 0, 0, 0x02}, true, false).is_discarded());
|
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9b, 0, 0, 0, 0, 0, 0, 0, 0x02}, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
||||||
|
TEST_CASE("regression test #5135 - destructor never allocates, even under memory pressure")
|
||||||
|
{
|
||||||
|
// Before the fix, ~basic_json() flattened a nested array/object into a
|
||||||
|
// heap-allocated std::vector to avoid recursing; that allocation could
|
||||||
|
// itself throw bad_alloc, which escapes a noexcept destructor and
|
||||||
|
// terminates the program. destroy() no longer allocates anything, so
|
||||||
|
// none of the sections below ever observe fail_next_allocation being
|
||||||
|
// consumed: CHECK(fail_next_allocation) confirms it was never touched.
|
||||||
|
|
||||||
|
SECTION("the original report: a small, mixed array/object nest")
|
||||||
|
{
|
||||||
|
failing_allocator_allocations = 0;
|
||||||
|
failing_allocator_deallocations = 0;
|
||||||
|
{
|
||||||
|
failing_json j = failing_json::array(
|
||||||
|
{
|
||||||
|
failing_json::array({1, 2}),
|
||||||
|
failing_json::object({{"key", failing_json::array({3})}})
|
||||||
|
});
|
||||||
|
fail_next_allocation = true;
|
||||||
|
} // j is destroyed here, with every further allocation set to fail
|
||||||
|
|
||||||
|
CHECK(fail_next_allocation);
|
||||||
|
fail_next_allocation = false;
|
||||||
|
CHECK(failing_allocator_deallocations > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("100000-deep nested array")
|
||||||
|
{
|
||||||
|
std::size_t allocations_before = 0;
|
||||||
|
{
|
||||||
|
failing_json j = make_deep_nest<failing_json>(100000, false);
|
||||||
|
allocations_before = failing_allocator_allocations;
|
||||||
|
fail_next_allocation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(fail_next_allocation);
|
||||||
|
fail_next_allocation = false;
|
||||||
|
CHECK(failing_allocator_allocations == allocations_before);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("100000-deep nested object")
|
||||||
|
{
|
||||||
|
std::size_t allocations_before = 0;
|
||||||
|
{
|
||||||
|
failing_json j = make_deep_nest<failing_json>(100000, true);
|
||||||
|
allocations_before = failing_allocator_allocations;
|
||||||
|
fail_next_allocation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(fail_next_allocation);
|
||||||
|
fail_next_allocation = false;
|
||||||
|
CHECK(failing_allocator_allocations == allocations_before);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("100000-deep nested ordered_json")
|
||||||
|
{
|
||||||
|
std::size_t allocations_before = 0;
|
||||||
|
{
|
||||||
|
failing_ordered_json j = make_deep_nest<failing_ordered_json>(100000, true);
|
||||||
|
allocations_before = failing_allocator_allocations;
|
||||||
|
fail_next_allocation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(fail_next_allocation);
|
||||||
|
fail_next_allocation = false;
|
||||||
|
CHECK(failing_allocator_allocations == allocations_before);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("wide and deep: 1000 arrays of 1000 elements, each a small nested object")
|
||||||
|
{
|
||||||
|
std::size_t allocations_before = 0;
|
||||||
|
{
|
||||||
|
failing_json wide = failing_json::array();
|
||||||
|
for (std::size_t i = 0; i < 1000; ++i)
|
||||||
|
{
|
||||||
|
failing_json inner = failing_json::array();
|
||||||
|
for (std::size_t k = 0; k < 1000; ++k)
|
||||||
|
{
|
||||||
|
inner.push_back(failing_json::object({{"a", 1}, {"b", failing_json::array({1, 2, 3})}}));
|
||||||
|
}
|
||||||
|
wide.push_back(std::move(inner));
|
||||||
|
}
|
||||||
|
|
||||||
|
allocations_before = failing_allocator_allocations;
|
||||||
|
fail_next_allocation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(fail_next_allocation);
|
||||||
|
fail_next_allocation = false;
|
||||||
|
CHECK(failing_allocator_allocations == allocations_before);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// a single-element chain of `depth` arrays, built iteratively (never
|
||||||
|
// recursing: each wrap only moves the previous, already-built value)
|
||||||
|
template<class BasicJsonType>
|
||||||
|
BasicJsonType make_single_chain(std::size_t depth)
|
||||||
|
{
|
||||||
|
BasicJsonType v = 1;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
BasicJsonType wrapper = BasicJsonType::array();
|
||||||
|
wrapper.push_back(std::move(v));
|
||||||
|
v = std::move(wrapper);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copies value first, to make sure nothing was corrupted by building it,
|
||||||
|
// then lets both the copy and the original destruct via normal scope exit
|
||||||
|
template<class BasicJsonType>
|
||||||
|
void check_destroy_edge_case(const BasicJsonType& value)
|
||||||
|
{
|
||||||
|
const BasicJsonType copy = value;
|
||||||
|
CHECK(copy == value);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("regression test #5135 - destroy() edge cases", BasicJsonType, json, ordered_json)
|
||||||
|
{
|
||||||
|
using binary_t = typename BasicJsonType::binary_t;
|
||||||
|
|
||||||
|
SECTION("mix of empty objects, empty arrays, non-empty containers, and scalars")
|
||||||
|
{
|
||||||
|
BasicJsonType root = BasicJsonType::array();
|
||||||
|
root.push_back(BasicJsonType::object());
|
||||||
|
root.push_back(BasicJsonType::array());
|
||||||
|
root.push_back(BasicJsonType::object({{"k", 1}}));
|
||||||
|
root.push_back(BasicJsonType::array({1, 2, 3}));
|
||||||
|
root.push_back(nullptr);
|
||||||
|
root.push_back(true);
|
||||||
|
root.push_back(42);
|
||||||
|
root.push_back(3.14);
|
||||||
|
root.push_back("a string");
|
||||||
|
root.push_back(BasicJsonType(binary_t({1, 2, 3})));
|
||||||
|
check_destroy_edge_case(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("container child in first position only")
|
||||||
|
{
|
||||||
|
BasicJsonType root = BasicJsonType::array({BasicJsonType::array({1, 2}), 3, 4, 5});
|
||||||
|
check_destroy_edge_case(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("container child in last position only")
|
||||||
|
{
|
||||||
|
BasicJsonType root = BasicJsonType::array({1, 2, 3, BasicJsonType::array({4, 5})});
|
||||||
|
check_destroy_edge_case(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("container children in first and last position")
|
||||||
|
{
|
||||||
|
BasicJsonType root = BasicJsonType::array({BasicJsonType::array({1}), 2, 3, BasicJsonType::array({4})});
|
||||||
|
check_destroy_edge_case(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("single-element chain, 1000 levels deep")
|
||||||
|
{
|
||||||
|
BasicJsonType root = make_single_chain<BasicJsonType>(1000);
|
||||||
|
check_destroy_edge_case(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("top-level empty array")
|
||||||
|
{
|
||||||
|
BasicJsonType root = BasicJsonType::array();
|
||||||
|
check_destroy_edge_case(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("top-level empty object")
|
||||||
|
{
|
||||||
|
BasicJsonType root = BasicJsonType::object();
|
||||||
|
check_destroy_edge_case(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("object whose last child is a non-empty array whose last child is an empty object")
|
||||||
|
{
|
||||||
|
BasicJsonType inner_array = BasicJsonType::array({1, 2, BasicJsonType::object()});
|
||||||
|
BasicJsonType root = BasicJsonType::object({{"a", 1}, {"b", inner_array}});
|
||||||
|
check_destroy_edge_case(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("destruction via erase() on a deeply nested child")
|
||||||
|
{
|
||||||
|
BasicJsonType root = BasicJsonType::array();
|
||||||
|
root.push_back(make_single_chain<BasicJsonType>(500));
|
||||||
|
root.push_back(BasicJsonType::object({{"k", BasicJsonType::array({1, 2, 3})}}));
|
||||||
|
// erase() must destroy the removed subtree without recursing or
|
||||||
|
// allocating beyond what erase() itself needs
|
||||||
|
root.erase(0);
|
||||||
|
CAPTURE(root.size())
|
||||||
|
CHECK(root.size() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("destruction via assignment on a deep tree")
|
||||||
|
{
|
||||||
|
BasicJsonType root = make_single_chain<BasicJsonType>(2000);
|
||||||
|
// assigning a new value destroys the old one in place
|
||||||
|
root = nullptr;
|
||||||
|
CHECK(root.is_null());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
@@ -35,10 +35,59 @@ TEST_CASE("UBJSON")
|
|||||||
{
|
{
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
// discarded values are not serialized
|
// a discarded value cannot be serialized to UBJSON
|
||||||
json const j = json::value_t::discarded;
|
json const j = json::value_t::discarded;
|
||||||
const auto result = json::to_ubjson(j);
|
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||||
CHECK(result.empty());
|
}
|
||||||
|
|
||||||
|
SECTION("discarded values nested in a container")
|
||||||
|
{
|
||||||
|
json const discarded = json::value_t::discarded;
|
||||||
|
|
||||||
|
SECTION("in an array")
|
||||||
|
{
|
||||||
|
json const j = {1, discarded, 2};
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("as an object value")
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
j["a"] = 1;
|
||||||
|
j["b"] = discarded;
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("nested deeper (array in object in array)")
|
||||||
|
{
|
||||||
|
json inner_array = {1, discarded};
|
||||||
|
json middle_object;
|
||||||
|
middle_object["x"] = inner_array;
|
||||||
|
json const j = {middle_object};
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_ubjson(j), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("optimized array of all-discarded elements")
|
||||||
|
{
|
||||||
|
json const j = {discarded, discarded};
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("null")
|
SECTION("null")
|
||||||
@@ -753,6 +802,32 @@ TEST_CASE("UBJSON")
|
|||||||
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec2), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A", json::parse_error);
|
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec2), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A", json::parse_error);
|
||||||
std::vector<uint8_t> const vec3 = {'H', 'i', 2, '1', '.'};
|
std::vector<uint8_t> const vec3 = {'H', 'i', 2, '1', '.'};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec3), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1.", json::parse_error);
|
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec3), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1.", json::parse_error);
|
||||||
|
// Reject NULs where they are read, including trailing NULs and payloads cut off after one.
|
||||||
|
SECTION("NUL in high-precision number (issue #5753)")
|
||||||
|
{
|
||||||
|
for (const auto& vec : std::vector<std::vector<uint8_t>>
|
||||||
|
{
|
||||||
|
{'H', 'i', 3, '1', 0, 'x'},
|
||||||
|
{'H', 'i', 2, '1', 0},
|
||||||
|
{'H', 'i', 3, '1', 0}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(vec)
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text; last byte: 0x00", json::parse_error);
|
||||||
|
CHECK(json::from_ubjson(vec, true, false).is_discarded());
|
||||||
|
CHECK(json::from_ubjson(vec, false, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> const nested = {'[', 'H', 'i', 3, '1', 0, 'x', ']'};
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(nested), "[json.exception.parse_error.115] parse error at byte 6: syntax error while parsing UBJSON high-precision number: invalid number text; last byte: 0x00", json::parse_error);
|
||||||
|
CHECK(json::from_ubjson(nested, true, false).is_discarded());
|
||||||
|
|
||||||
|
std::vector<uint8_t> const valid = {'H', 'i', 1, '1'};
|
||||||
|
const auto j = json::from_ubjson(valid);
|
||||||
|
CHECK(j.is_number_unsigned());
|
||||||
|
CHECK(j == json(1));
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> const vec_overflow = {'H', 'i', 5, '1', 'e', '4', '0', '0'};
|
std::vector<uint8_t> const vec_overflow = {'H', 'i', 5, '1', 'e', '4', '0', '0'};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec_overflow), "[json.exception.out_of_range.406] number overflow parsing '1e400'", json::out_of_range&);
|
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec_overflow), "[json.exception.out_of_range.406] number overflow parsing '1e400'", json::out_of_range&);
|
||||||
std::vector<uint8_t> const vec4 = {'H', 2, '1', '0'};
|
std::vector<uint8_t> const vec4 = {'H', 2, '1', '0'};
|
||||||
@@ -2099,9 +2174,14 @@ TEST_CASE("UBJSON")
|
|||||||
|
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
{
|
{
|
||||||
|
// a discarded value cannot be serialized to UBJSON, even as part
|
||||||
|
// of an optimized array of a single (here: valueless) type
|
||||||
json const j = {json::value_t::discarded, json::value_t::discarded};
|
json const j = {json::value_t::discarded, json::value_t::discarded};
|
||||||
std::vector<uint8_t> expected = {'[', '$', 'N', '#', 'i', 2};
|
#if JSON_DIAGNOSTICS
|
||||||
CHECK(json::to_ubjson(j, true, true) == expected);
|
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to UBJSON", json::type_error&);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_ubjson(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to UBJSON", json::type_error&);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-3
@@ -23,6 +23,7 @@ using nlohmann::json;
|
|||||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -684,8 +685,7 @@ static std::ostream& operator<<(std::ostream& os, small_pod l)
|
|||||||
TEST_CASE("custom serializer for pods" * doctest::test_suite("udt"))
|
TEST_CASE("custom serializer for pods" * doctest::test_suite("udt"))
|
||||||
{
|
{
|
||||||
using custom_json =
|
using custom_json =
|
||||||
nlohmann::basic_json<std::map, std::vector, std::string, bool,
|
nlohmann::json::with_json_serializer_t<pod_serializer>;
|
||||||
std::int64_t, std::uint64_t, double, std::allocator, pod_serializer>;
|
|
||||||
|
|
||||||
auto p = udt::small_pod{42, '/', 42};
|
auto p = udt::small_pod{42, '/', 42};
|
||||||
custom_json const j = p;
|
custom_json const j = p;
|
||||||
@@ -703,7 +703,7 @@ TEST_CASE("custom serializer for pods" * doctest::test_suite("udt"))
|
|||||||
template <typename T, typename>
|
template <typename T, typename>
|
||||||
struct another_adl_serializer;
|
struct another_adl_serializer;
|
||||||
|
|
||||||
using custom_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, double, std::allocator, another_adl_serializer>;
|
using custom_json = nlohmann::json::with_json_serializer_t<another_adl_serializer>;
|
||||||
|
|
||||||
template <typename T, typename>
|
template <typename T, typename>
|
||||||
struct another_adl_serializer
|
struct another_adl_serializer
|
||||||
@@ -734,6 +734,80 @@ TEST_CASE("custom serializer that does adl by default" * doctest::test_suite("ud
|
|||||||
CHECK(me == cj.get<udt::person>());
|
CHECK(me == cj.get<udt::person>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("with_*_t aliases" * doctest::test_suite("udt"))
|
||||||
|
{
|
||||||
|
// a custom base class used to check with_base_class_t
|
||||||
|
struct custom_base_class {};
|
||||||
|
|
||||||
|
CHECK(std::is_same<json::with_object_t<std::deque>,
|
||||||
|
nlohmann::basic_json<std::deque, std::vector, std::string, bool,
|
||||||
|
std::int64_t, std::uint64_t, double, std::allocator,
|
||||||
|
nlohmann::adl_serializer, std::vector<std::uint8_t>>>::value);
|
||||||
|
|
||||||
|
CHECK(std::is_same<json::with_array_t<std::deque>,
|
||||||
|
nlohmann::basic_json<std::map, std::deque, std::string, bool,
|
||||||
|
std::int64_t, std::uint64_t, double, std::allocator,
|
||||||
|
nlohmann::adl_serializer, std::vector<std::uint8_t>>>::value);
|
||||||
|
|
||||||
|
CHECK(std::is_same<json::with_string_t<std::wstring>,
|
||||||
|
nlohmann::basic_json<std::map, std::vector, std::wstring, bool,
|
||||||
|
std::int64_t, std::uint64_t, double, std::allocator,
|
||||||
|
nlohmann::adl_serializer, std::vector<std::uint8_t>>>::value);
|
||||||
|
|
||||||
|
CHECK(std::is_same<json::with_boolean_t<int>,
|
||||||
|
nlohmann::basic_json<std::map, std::vector, std::string, int,
|
||||||
|
std::int64_t, std::uint64_t, double, std::allocator,
|
||||||
|
nlohmann::adl_serializer, std::vector<std::uint8_t>>>::value);
|
||||||
|
|
||||||
|
CHECK(std::is_same<json::with_integers_t<std::int32_t, std::uint32_t>,
|
||||||
|
nlohmann::basic_json<std::map, std::vector, std::string, bool,
|
||||||
|
std::int32_t, std::uint32_t, double, std::allocator,
|
||||||
|
nlohmann::adl_serializer, std::vector<std::uint8_t>>>::value);
|
||||||
|
|
||||||
|
CHECK(std::is_same<json::with_float_t<float>,
|
||||||
|
nlohmann::basic_json<std::map, std::vector, std::string, bool,
|
||||||
|
std::int64_t, std::uint64_t, float, std::allocator,
|
||||||
|
nlohmann::adl_serializer, std::vector<std::uint8_t>>>::value);
|
||||||
|
|
||||||
|
CHECK(std::is_same<json::with_allocator_t<std::allocator>,
|
||||||
|
nlohmann::basic_json<std::map, std::vector, std::string, bool,
|
||||||
|
std::int64_t, std::uint64_t, double, std::allocator,
|
||||||
|
nlohmann::adl_serializer, std::vector<std::uint8_t>>>::value);
|
||||||
|
|
||||||
|
CHECK(std::is_same<json::with_json_serializer_t<nlohmann::adl_serializer>,
|
||||||
|
nlohmann::basic_json<std::map, std::vector, std::string, bool,
|
||||||
|
std::int64_t, std::uint64_t, double, std::allocator,
|
||||||
|
nlohmann::adl_serializer, std::vector<std::uint8_t>>>::value);
|
||||||
|
|
||||||
|
CHECK(std::is_same<json::with_binary_t<std::vector<char>>,
|
||||||
|
nlohmann::basic_json<std::map, std::vector, std::string, bool,
|
||||||
|
std::int64_t, std::uint64_t, double, std::allocator,
|
||||||
|
nlohmann::adl_serializer, std::vector<char>>>::value);
|
||||||
|
|
||||||
|
CHECK(std::is_same<json::with_base_class_t<custom_base_class>,
|
||||||
|
nlohmann::basic_json<std::map, std::vector, std::string, bool,
|
||||||
|
std::int64_t, std::uint64_t, double, std::allocator,
|
||||||
|
nlohmann::adl_serializer, std::vector<std::uint8_t>, custom_base_class>>::value);
|
||||||
|
|
||||||
|
// with_string_t on ordered_json must keep ordered_map as the object type
|
||||||
|
CHECK(std::is_same<nlohmann::ordered_json::with_string_t<std::wstring>,
|
||||||
|
nlohmann::basic_json<nlohmann::ordered_map, std::vector, std::wstring, bool,
|
||||||
|
std::int64_t, std::uint64_t, double, std::allocator,
|
||||||
|
nlohmann::adl_serializer, std::vector<std::uint8_t>>>::value);
|
||||||
|
|
||||||
|
// the aliases are members of the resulting type, so they can be chained
|
||||||
|
CHECK(std::is_same<json::with_integers_t<int, unsigned int>::with_float_t<float>,
|
||||||
|
nlohmann::basic_json<std::map, std::vector, std::string, bool,
|
||||||
|
int, unsigned int, float, std::allocator,
|
||||||
|
nlohmann::adl_serializer, std::vector<std::uint8_t>>>::value);
|
||||||
|
CHECK(std::is_same<json::with_float_t<float>::with_integers_t<int, unsigned int>,
|
||||||
|
json::with_integers_t<int, unsigned int>::with_float_t<float>>::value);
|
||||||
|
|
||||||
|
// replacing the object type of json with ordered_map yields ordered_json
|
||||||
|
CHECK(std::is_same<json::with_object_t<nlohmann::ordered_map>, nlohmann::ordered_json>::value);
|
||||||
|
CHECK(std::is_same<nlohmann::ordered_json::with_object_t<std::map>, json>::value);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("different basic_json types conversions")
|
TEST_CASE("different basic_json types conversions")
|
||||||
{
|
{
|
||||||
SECTION("null")
|
SECTION("null")
|
||||||
|
|||||||
@@ -36,31 +36,5 @@
|
|||||||
</Expand>
|
</Expand>
|
||||||
</Type>
|
</Type>
|
||||||
|
|
||||||
<!-- Fallback for when the basic_json entry above does not match: json_default_base is the (empty) default
|
|
||||||
base class of basic_json, and base class visualizers are inherited by derived types and evaluated
|
|
||||||
against the derived object, so m_data is accessible here. The class lives in {{ ns }} after
|
|
||||||
3.12.0 (#5238) and in {{ ns }}::detail up to 3.12.0, so both names are listed. -->
|
|
||||||
{% for default_base in ['json_default_base', 'detail::json_default_base'] %}
|
|
||||||
<Type Name="{{ ns }}::{{ default_base }}">
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::null">null</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
|
|
||||||
<DisplayString Condition="m_data.m_type == {{ ns }}::detail::value_t::discarded">discarded</DisplayString>
|
|
||||||
<Expand>
|
|
||||||
<ExpandedItem Condition="m_data.m_type == {{ ns }}::detail::value_t::object">
|
|
||||||
*(m_data.m_value.object),view(simple)
|
|
||||||
</ExpandedItem>
|
|
||||||
<ExpandedItem Condition="m_data.m_type == {{ ns }}::detail::value_t::array">
|
|
||||||
*(m_data.m_value.array),view(simple)
|
|
||||||
</ExpandedItem>
|
|
||||||
</Expand>
|
|
||||||
</Type>
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</AutoVisualizer>
|
</AutoVisualizer>
|
||||||
Reference in new issue
Block a user