mirror of
https://github.com/nlohmann/json.git
synced 2026-10-06 22:47:13 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65bcce35ba | ||
|
|
54beb8ffae |
No files matched your search
@@ -71,38 +71,3 @@ 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
|
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
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,7 +80,6 @@ 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/json"
|
repository-code: "https://github.com/nlohmann"
|
||||||
url: https://json.nlohmann.me
|
url: https://json.nlohmann.me
|
||||||
+1
-4
@@ -42,11 +42,8 @@ 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.toml`
|
### `.reuse/dep5`
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
- [REUSE.toml](https://reuse.software/spec-3.3/#reusetoml)
|
- [DEP5](https://reuse.software/spec-3.2/#dep5-deprecated)
|
||||||
- [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.toml` is predetermined by REUSE. Alternatively, a `.reuse/dep5` file (deprecated) can be used.
|
> The filename `.reuse/dep5` is predetermined by REUSE. Alternatively, a `REUSE.toml` file can be used.
|
||||||
|
|
||||||
### `.reuse/templates`
|
### `.reuse/templates`
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,6 @@
|
|||||||
# 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
|
||||||
@@ -177,8 +174,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 src/modules -type f) json
|
rsync -R $(shell find LICENSE.MIT nlohmann_json.natvis CMakeLists.txt cmake/*.in include single_include -type f) json
|
||||||
$(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
|
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
|
||||||
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(.v9), .visionOS(.v1)
|
.iOS(.v12), .macOS(.v10_13), .tvOS(.v12), .watchOS(.v4), .visionOS(.v1)
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(name: "json", targets: ["json"])
|
.library(name: "json", targets: ["json"])
|
||||||
|
|||||||
@@ -1202,18 +1202,15 @@ 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 - 16.2 (and possibly later)
|
- GCC 4.8 - 14.2 (and possibly later)
|
||||||
- Clang 3.4 - 22.1 (and possibly later)
|
- Clang 3.4 - 21.0 (and possibly later)
|
||||||
- Apple Clang 15.0 - 21.0 (and possibly later)
|
- Apple Clang 9.1 - 16.0 (and possibly later)
|
||||||
- Intel C++ Compiler Classic (icpc) 2021.10
|
- Intel C++ Compiler 17.0.2 (and possibly later)
|
||||||
- Intel oneAPI DPC++/C++ Compiler (icpx) 2025.3 (and possibly later)
|
- Nvidia CUDA Compiler 11.0.221 (and possibly later)
|
||||||
- NVIDIA CUDA Compiler (nvcc) 11.8 - 12.6 (and possibly later)
|
- Microsoft Visual C++ 2015 / Build Tools 14.0.25123.0 (and possibly later)
|
||||||
- NVIDIA HPC SDK C++ Compiler (nvc++) 25.5 (and possibly later)
|
- Microsoft Visual C++ 2017 / Build Tools 15.5.180.51428 (and possibly later)
|
||||||
- Microsoft Visual C++ 2015 / MSVC 19.0 (and possibly later)
|
- Microsoft Visual C++ 2019 / Build Tools 16.3.1+1def00d3d (and possibly later)
|
||||||
- Microsoft Visual C++ 2017 / MSVC 19.16 (and possibly later)
|
- Microsoft Visual C++ 2022 / Build Tools 19.30.30709.0 (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.
|
||||||
|
|
||||||
@@ -1404,7 +1401,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.toml` contains an overview of all files' copyrights and licenses.
|
- File `.reuse/dep5` 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
@@ -1,52 +0,0 @@
|
|||||||
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,7 +48,6 @@ 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 path = "**" rule in REUSE.toml) -- keep
|
# already covered by the blanket "Files: *" rule in .reuse/dep5) -- 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,7 +131,6 @@ 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,9 +109,6 @@ 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,9 +7,8 @@ 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, so `#!json null`, `#!cpp false`, and numbers all hash differently from
|
type of the JSON value is taken into account to have different hash values for `#!json null`, `#!cpp 0`, `#!cpp 0U`, and
|
||||||
each other. Numbers that compare equal under [`operator==`](operator_eq.md) always hash equally, regardless of
|
`#!cpp false`, etc.
|
||||||
whether they are stored as `#!cpp 0`, `#!cpp 0U`, or `#!cpp 0.0`.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -37,5 +36,3 @@ whether they are stored as `#!cpp 0`, `#!cpp 0U`, or `#!cpp 0.0`.
|
|||||||
|
|
||||||
- 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.
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
# <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.
|
|
||||||
@@ -13,7 +13,19 @@ 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::json::with_base_class_t<visitor_adaptor_with_metadata>;
|
using json = 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>,
|
||||||
|
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,7 +11,6 @@ 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,9 +1,8 @@
|
|||||||
hash(null) = 2654435769
|
hash(null) = 2654435769
|
||||||
hash(false) = 2654436030
|
hash(false) = 2654436030
|
||||||
hash(0) = 2654436221
|
hash(0) = 2654436095
|
||||||
hash(0U) = 2654436221
|
hash(0U) = 2654436156
|
||||||
hash(0.0) = 2654436221
|
hash("") = 6142509191626859748
|
||||||
hash("") = 11160318156688833227
|
|
||||||
hash({}) = 2654435832
|
hash({}) = 2654435832
|
||||||
hash([]) = 2654435899
|
hash([]) = 2654435899
|
||||||
hash({"hello": "world"}) = 3701319991624763853
|
hash({"hello": "world"}) = 4469488738203676328
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"a":2,"b":3,"c":1}
|
|
||||||
@@ -131,6 +131,7 @@ The library maps CBOR types to JSON value types as follows:
|
|||||||
| Byte string | binary | 0x59 |
|
| Byte string | binary | 0x59 |
|
||||||
| Byte string | binary | 0x5A |
|
| Byte string | binary | 0x5A |
|
||||||
| Byte string | binary | 0x5B |
|
| Byte string | binary | 0x5B |
|
||||||
|
| Byte string | binary | 0x5F |
|
||||||
| UTF-8 string | string | 0x60..0x77 |
|
| UTF-8 string | string | 0x60..0x77 |
|
||||||
| UTF-8 string | string | 0x78 |
|
| UTF-8 string | string | 0x78 |
|
||||||
| UTF-8 string | string | 0x79 |
|
| UTF-8 string | string | 0x79 |
|
||||||
@@ -156,6 +157,9 @@ The library maps CBOR types to JSON value types as follows:
|
|||||||
| Single-Precision Float | number_float | 0xFA |
|
| Single-Precision Float | number_float | 0xFA |
|
||||||
| Double-Precision Float | number_float | 0xFB |
|
| Double-Precision Float | number_float | 0xFB |
|
||||||
|
|
||||||
|
Indefinite-length UTF-8 strings (0x7F) and byte strings (0x5F) are supported. Each chunk must be a definite-length
|
||||||
|
string of the same major type, as required by [RFC 8949, Section 3.2.3](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.2.3).
|
||||||
|
|
||||||
!!! warning "Incomplete mapping"
|
!!! warning "Incomplete mapping"
|
||||||
|
|
||||||
The mapping is **incomplete** in the sense that not all CBOR types can be converted to a JSON value. The following CBOR types are not supported and will yield parse errors:
|
The mapping is **incomplete** in the sense that not all CBOR types can be converted to a JSON value. The following CBOR types are not supported and will yield parse errors:
|
||||||
|
|||||||
@@ -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 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.
|
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.
|
||||||
|
|
||||||
### `JSON_CI`
|
### `JSON_CI`
|
||||||
|
|
||||||
|
|||||||
@@ -232,7 +232,6 @@ 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.6.0 # plugin "htmlproofer"
|
mkdocs-htmlproofer-plugin==1.5.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.2",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
|||||||
@@ -35,10 +35,8 @@ 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, so null, false, and numbers all
|
type of the JSON value is taken into account to have different hash values for
|
||||||
hash differently from each other, but any two numbers that compare equal
|
null, 0, 0U, and false, etc.
|
||||||
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
|
||||||
@@ -57,6 +55,8 @@ 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,20 +113,21 @@ 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:
|
||||||
{
|
{
|
||||||
// operator== compares numbers by their mathematical value across
|
const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());
|
||||||
// number_integer, number_unsigned, and number_float, so equal
|
return combine(type, h);
|
||||||
// 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.
|
|
||||||
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:
|
||||||
|
|||||||
@@ -1072,6 +1072,20 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief reports a nested indefinite-length CBOR string or byte array
|
||||||
|
@param[in] type_name name of the rejected string type
|
||||||
|
@param[in] context parsing context for the error message
|
||||||
|
@return whether the SAX consumer accepts the parse error
|
||||||
|
*/
|
||||||
|
bool cbor_indefinite_string_error(const char* type_name, const char* context)
|
||||||
|
{
|
||||||
|
auto last_token = get_token_string();
|
||||||
|
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||||
|
exception_message(concat("indefinite-length ", type_name,
|
||||||
|
" is not allowed inside indefinite-length ", type_name, "; last byte: 0x", last_token), context), nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reads a definite-length CBOR string
|
@brief reads a definite-length CBOR string
|
||||||
|
|
||||||
@@ -1081,12 +1095,13 @@ class binary_reader
|
|||||||
into the same string.
|
into the same string.
|
||||||
|
|
||||||
@param[out] result string the bytes are appended to
|
@param[out] result string the bytes are appended to
|
||||||
|
@param[in] inside_indefinite whether the bytes belong to an indefinite-length string
|
||||||
|
|
||||||
@return whether string creation completed
|
@return whether string creation completed
|
||||||
|
|
||||||
@pre @a current is not EOF
|
@pre @a current is not EOF
|
||||||
*/
|
*/
|
||||||
bool get_cbor_string_chunk(string_t& result)
|
bool get_cbor_string_chunk(string_t& result, const bool inside_indefinite)
|
||||||
{
|
{
|
||||||
switch (current)
|
switch (current)
|
||||||
{
|
{
|
||||||
@@ -1147,7 +1162,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
auto last_token = get_token_string();
|
auto last_token = get_token_string();
|
||||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||||
exception_message(concat("expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x", last_token), "string"), nullptr));
|
exception_message(concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", last_token), "string"), nullptr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1165,13 +1180,9 @@ class binary_reader
|
|||||||
*/
|
*/
|
||||||
bool get_cbor_string(string_t& result, const char* context = "string")
|
bool get_cbor_string(string_t& result, const char* context = "string")
|
||||||
{
|
{
|
||||||
// number of indefinite-length strings that have been opened and not
|
// read chunks iteratively, but reject a second indefinite-length
|
||||||
// closed yet. RFC 8949, Section 3.2.3 does not permit nesting them,
|
// level as required by RFC 8949, Section 3.2.3
|
||||||
// but this reader has always accepted it, so the open levels are
|
bool indefinite = false;
|
||||||
// counted instead of recursed through, which overflowed the stack for
|
|
||||||
// an input of repeated 0x7F bytes (see #5104). Every chunk is appended
|
|
||||||
// to the same result, so no per-level state is needed.
|
|
||||||
std::size_t open = 0;
|
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -1182,29 +1193,28 @@ class binary_reader
|
|||||||
|
|
||||||
if (current == 0x7F) // UTF-8 string (indefinite length)
|
if (current == 0x7F) // UTF-8 string (indefinite length)
|
||||||
{
|
{
|
||||||
++open;
|
if (JSON_HEDLEY_UNLIKELY(indefinite))
|
||||||
get();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// a break marker closes the innermost indefinite-length string;
|
|
||||||
// outside of one it is not a string and falls through to the error
|
|
||||||
if (open != 0 && current == 0xFF)
|
|
||||||
{
|
|
||||||
if (--open == 0)
|
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, context);
|
return cbor_indefinite_string_error("string", "string");
|
||||||
}
|
}
|
||||||
|
indefinite = true;
|
||||||
get();
|
get();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result)))
|
// a break marker closes the indefinite-length string; outside
|
||||||
|
// of one it falls through to the error below
|
||||||
|
if (indefinite && current == 0xFF)
|
||||||
|
{
|
||||||
|
return check_string_utf8(result, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, indefinite)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (open == 0)
|
if (!indefinite)
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, context);
|
return check_string_utf8(result, context);
|
||||||
}
|
}
|
||||||
@@ -1296,12 +1306,13 @@ class binary_reader
|
|||||||
read into the same byte array.
|
read into the same byte array.
|
||||||
|
|
||||||
@param[out] result byte array the bytes are appended to
|
@param[out] result byte array the bytes are appended to
|
||||||
|
@param[in] inside_indefinite whether the bytes belong to an indefinite-length string
|
||||||
|
|
||||||
@return whether byte array creation completed
|
@return whether byte array creation completed
|
||||||
|
|
||||||
@pre @a current is not EOF
|
@pre @a current is not EOF
|
||||||
*/
|
*/
|
||||||
bool get_cbor_binary_chunk(binary_t& result)
|
bool get_cbor_binary_chunk(binary_t& result, const bool inside_indefinite)
|
||||||
{
|
{
|
||||||
switch (current)
|
switch (current)
|
||||||
{
|
{
|
||||||
@@ -1366,7 +1377,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
auto last_token = get_token_string();
|
auto last_token = get_token_string();
|
||||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||||
exception_message(concat("expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x", last_token), "binary"), nullptr));
|
exception_message(concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", last_token), "binary"), nullptr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1384,9 +1395,9 @@ class binary_reader
|
|||||||
*/
|
*/
|
||||||
bool get_cbor_binary(binary_t& result)
|
bool get_cbor_binary(binary_t& result)
|
||||||
{
|
{
|
||||||
// the open indefinite-length byte arrays are counted rather than
|
// read chunks iteratively, but reject a second indefinite-length
|
||||||
// recursed through, for the reason given in @ref get_cbor_string
|
// level as required by RFC 8949, Section 3.2.3
|
||||||
std::size_t open = 0;
|
bool indefinite = false;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -1397,29 +1408,28 @@ class binary_reader
|
|||||||
|
|
||||||
if (current == 0x5F) // Binary data (indefinite length)
|
if (current == 0x5F) // Binary data (indefinite length)
|
||||||
{
|
{
|
||||||
++open;
|
if (JSON_HEDLEY_UNLIKELY(indefinite))
|
||||||
get();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// a break marker closes the innermost indefinite-length byte
|
|
||||||
// array; outside of one it falls through to the error below
|
|
||||||
if (open != 0 && current == 0xFF)
|
|
||||||
{
|
|
||||||
if (--open == 0)
|
|
||||||
{
|
{
|
||||||
return true;
|
return cbor_indefinite_string_error("binary array", "binary");
|
||||||
}
|
}
|
||||||
|
indefinite = true;
|
||||||
get();
|
get();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_binary_chunk(result)))
|
// a break marker closes the indefinite-length string; outside
|
||||||
|
// of one it falls through to the error below
|
||||||
|
if (indefinite && current == 0xFF)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_binary_chunk(result, indefinite)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (open == 0)
|
if (!indefinite)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -3249,12 +3259,6 @@ 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,39 +2021,6 @@ 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 == '/')
|
||||||
{
|
{
|
||||||
@@ -2133,6 +2100,7 @@ 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_expecting(token_type::name_separator)))
|
if (JSON_HEDLEY_UNLIKELY(get_token() != 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_expecting(token_type::value_separator))
|
if (get_token() == 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_expecting(token_type::value_separator))
|
if (get_token() == token_type::value_separator)
|
||||||
{
|
{
|
||||||
get_token();
|
get_token();
|
||||||
|
|
||||||
@@ -484,7 +484,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
if (JSON_HEDLEY_UNLIKELY(get_token() != 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,13 +528,6 @@ 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 ";
|
||||||
|
|||||||
+24
-125
@@ -226,70 +226,6 @@ 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 //
|
||||||
////////////////
|
////////////////
|
||||||
@@ -697,62 +633,26 @@ private:
|
|||||||
return v.m_data.m_value.array->empty();
|
return v.m_data.m_value.array->empty();
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
return v.m_data.m_value.object->empty();
|
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:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The walk in destroy_container() may take the children of a
|
static basic_json& last_child(basic_json& v)
|
||||||
// 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)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
return v.m_data.m_value.array->back();
|
return v.m_data.m_value.array->back();
|
||||||
}
|
}
|
||||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
return walk_child_it(*v.m_data.m_value.object)->second;
|
return v.m_data.m_value.object->rbegin()->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
// removes walk_child(v) from a non-empty array/object v; this never
|
// removes the last child of a non-empty array/object v; this never
|
||||||
// allocates, and since it is only ever called when that child is a
|
// allocates, and since it is only ever called when that child is a
|
||||||
// scalar or an already-empty array/object, destroying it never
|
// scalar or an already-empty array/object, destroying it never
|
||||||
// recurses more than one level deep (see destroy() below)
|
// recurses more than one level deep (see destroy() below)
|
||||||
static void pop_walk_child(basic_json& v)
|
static void pop_last_child(basic_json& v)
|
||||||
{
|
{
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
@@ -761,7 +661,9 @@ private:
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
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));
|
// erase() needs a forward iterator, so std::prev(end()) is
|
||||||
|
// used here rather than rbegin() (see last_child() above)
|
||||||
|
v.m_data.m_value.object->erase(std::prev(v.m_data.m_value.object->end()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -832,17 +734,14 @@ public:
|
|||||||
// bad_alloc, which would escape this noexcept destructor and
|
// bad_alloc, which would escape this noexcept destructor and
|
||||||
// terminate the program (#5135).
|
// terminate the program (#5135).
|
||||||
//
|
//
|
||||||
// Instead, walk down a chain of children (always the one
|
// Instead, walk down the "last child" chain, reversing links
|
||||||
// walk_child() picks), reversing links as we go: cur is the
|
// as we go: cur is the container currently being emptied,
|
||||||
// container currently being emptied, and prev is its parent
|
// and prev is its parent (value_t::null when there is none).
|
||||||
// (value_t::null when there is none). Each parent's
|
// Each parent's last child slot doubles as storage for that
|
||||||
// walk_child() slot doubles as storage for that parent's own
|
// parent's own parent link while we are below it, so no
|
||||||
// parent link while we are below it, so no extra memory is
|
// extra memory is needed. We only ever remove a child once
|
||||||
// needed; the parent is not modified meanwhile, so
|
// it is a scalar or an empty array/object, which neither
|
||||||
// walk_child() finds that same slot again on the way up. We
|
// allocates nor recurses more than one level deep.
|
||||||
// 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
|
// This json_value is not itself a basic_json, so the
|
||||||
// top-level container is first moved into a local stand-in
|
// top-level container is first moved into a local stand-in
|
||||||
@@ -868,11 +767,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ascend: detach the grandparent link from prev's
|
// ascend: detach the grandparent link from prev's
|
||||||
// walk_child() slot, drop that (now null) slot, free cur
|
// last slot, drop that (now null) slot, free cur
|
||||||
// (it is empty), then move up one level
|
// (it is empty), then move up one level
|
||||||
basic_json gp;
|
basic_json gp;
|
||||||
take(gp, walk_child(prev));
|
take(gp, last_child(prev));
|
||||||
pop_walk_child(prev);
|
pop_last_child(prev);
|
||||||
|
|
||||||
free_container(cur);
|
free_container(cur);
|
||||||
|
|
||||||
@@ -881,21 +780,21 @@ public:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
basic_json& cur_child_ref = walk_child(cur);
|
basic_json& cur_last_ref = last_child(cur);
|
||||||
|
|
||||||
if (has_no_children(cur_child_ref))
|
if (has_no_children(cur_last_ref))
|
||||||
{
|
{
|
||||||
// scalar, or already-empty array/object
|
// scalar, or already-empty array/object
|
||||||
pop_walk_child(cur);
|
pop_last_child(cur);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// descend into the non-empty child, reversing the
|
// descend into the non-empty last child, reversing the
|
||||||
// link: its slot takes over prev, and the child becomes
|
// link: its slot takes over prev, and the child becomes
|
||||||
// the new cur
|
// the new cur
|
||||||
basic_json tmp;
|
basic_json tmp;
|
||||||
take(tmp, cur_child_ref);
|
take(tmp, cur_last_ref);
|
||||||
take(cur_child_ref, prev);
|
take(cur_last_ref, prev);
|
||||||
take(prev, cur);
|
take(prev, cur);
|
||||||
take(cur, tmp);
|
take(cur, tmp);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7636,10 +7636,8 @@ 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, so null, false, and numbers all
|
type of the JSON value is taken into account to have different hash values for
|
||||||
hash differently from each other, but any two numbers that compare equal
|
null, 0, 0U, and false, etc.
|
||||||
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
|
||||||
@@ -7658,6 +7656,8 @@ 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,20 +7714,21 @@ 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:
|
||||||
{
|
{
|
||||||
// operator== compares numbers by their mathematical value across
|
const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());
|
||||||
// number_integer, number_unsigned, and number_float, so equal
|
return combine(type, h);
|
||||||
// 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.
|
|
||||||
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:
|
||||||
@@ -12312,39 +12313,6 @@ 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 == '/')
|
||||||
{
|
{
|
||||||
@@ -12424,6 +12392,7 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
/// input adapter
|
/// input adapter
|
||||||
InputAdapterType ia;
|
InputAdapterType ia;
|
||||||
|
|
||||||
@@ -14820,6 +14789,20 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief reports a nested indefinite-length CBOR string or byte array
|
||||||
|
@param[in] type_name name of the rejected string type
|
||||||
|
@param[in] context parsing context for the error message
|
||||||
|
@return whether the SAX consumer accepts the parse error
|
||||||
|
*/
|
||||||
|
bool cbor_indefinite_string_error(const char* type_name, const char* context)
|
||||||
|
{
|
||||||
|
auto last_token = get_token_string();
|
||||||
|
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||||
|
exception_message(concat("indefinite-length ", type_name,
|
||||||
|
" is not allowed inside indefinite-length ", type_name, "; last byte: 0x", last_token), context), nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reads a definite-length CBOR string
|
@brief reads a definite-length CBOR string
|
||||||
|
|
||||||
@@ -14829,12 +14812,13 @@ class binary_reader
|
|||||||
into the same string.
|
into the same string.
|
||||||
|
|
||||||
@param[out] result string the bytes are appended to
|
@param[out] result string the bytes are appended to
|
||||||
|
@param[in] inside_indefinite whether the bytes belong to an indefinite-length string
|
||||||
|
|
||||||
@return whether string creation completed
|
@return whether string creation completed
|
||||||
|
|
||||||
@pre @a current is not EOF
|
@pre @a current is not EOF
|
||||||
*/
|
*/
|
||||||
bool get_cbor_string_chunk(string_t& result)
|
bool get_cbor_string_chunk(string_t& result, const bool inside_indefinite)
|
||||||
{
|
{
|
||||||
switch (current)
|
switch (current)
|
||||||
{
|
{
|
||||||
@@ -14895,7 +14879,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
auto last_token = get_token_string();
|
auto last_token = get_token_string();
|
||||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||||
exception_message(concat("expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x", last_token), "string"), nullptr));
|
exception_message(concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", last_token), "string"), nullptr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14913,13 +14897,9 @@ class binary_reader
|
|||||||
*/
|
*/
|
||||||
bool get_cbor_string(string_t& result, const char* context = "string")
|
bool get_cbor_string(string_t& result, const char* context = "string")
|
||||||
{
|
{
|
||||||
// number of indefinite-length strings that have been opened and not
|
// read chunks iteratively, but reject a second indefinite-length
|
||||||
// closed yet. RFC 8949, Section 3.2.3 does not permit nesting them,
|
// level as required by RFC 8949, Section 3.2.3
|
||||||
// but this reader has always accepted it, so the open levels are
|
bool indefinite = false;
|
||||||
// counted instead of recursed through, which overflowed the stack for
|
|
||||||
// an input of repeated 0x7F bytes (see #5104). Every chunk is appended
|
|
||||||
// to the same result, so no per-level state is needed.
|
|
||||||
std::size_t open = 0;
|
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -14930,29 +14910,28 @@ class binary_reader
|
|||||||
|
|
||||||
if (current == 0x7F) // UTF-8 string (indefinite length)
|
if (current == 0x7F) // UTF-8 string (indefinite length)
|
||||||
{
|
{
|
||||||
++open;
|
if (JSON_HEDLEY_UNLIKELY(indefinite))
|
||||||
get();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// a break marker closes the innermost indefinite-length string;
|
|
||||||
// outside of one it is not a string and falls through to the error
|
|
||||||
if (open != 0 && current == 0xFF)
|
|
||||||
{
|
|
||||||
if (--open == 0)
|
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, context);
|
return cbor_indefinite_string_error("string", "string");
|
||||||
}
|
}
|
||||||
|
indefinite = true;
|
||||||
get();
|
get();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result)))
|
// a break marker closes the indefinite-length string; outside
|
||||||
|
// of one it falls through to the error below
|
||||||
|
if (indefinite && current == 0xFF)
|
||||||
|
{
|
||||||
|
return check_string_utf8(result, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_string_chunk(result, indefinite)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (open == 0)
|
if (!indefinite)
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, context);
|
return check_string_utf8(result, context);
|
||||||
}
|
}
|
||||||
@@ -15044,12 +15023,13 @@ class binary_reader
|
|||||||
read into the same byte array.
|
read into the same byte array.
|
||||||
|
|
||||||
@param[out] result byte array the bytes are appended to
|
@param[out] result byte array the bytes are appended to
|
||||||
|
@param[in] inside_indefinite whether the bytes belong to an indefinite-length string
|
||||||
|
|
||||||
@return whether byte array creation completed
|
@return whether byte array creation completed
|
||||||
|
|
||||||
@pre @a current is not EOF
|
@pre @a current is not EOF
|
||||||
*/
|
*/
|
||||||
bool get_cbor_binary_chunk(binary_t& result)
|
bool get_cbor_binary_chunk(binary_t& result, const bool inside_indefinite)
|
||||||
{
|
{
|
||||||
switch (current)
|
switch (current)
|
||||||
{
|
{
|
||||||
@@ -15114,7 +15094,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
auto last_token = get_token_string();
|
auto last_token = get_token_string();
|
||||||
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,
|
||||||
exception_message(concat("expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x", last_token), "binary"), nullptr));
|
exception_message(concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", last_token), "binary"), nullptr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15132,9 +15112,9 @@ class binary_reader
|
|||||||
*/
|
*/
|
||||||
bool get_cbor_binary(binary_t& result)
|
bool get_cbor_binary(binary_t& result)
|
||||||
{
|
{
|
||||||
// the open indefinite-length byte arrays are counted rather than
|
// read chunks iteratively, but reject a second indefinite-length
|
||||||
// recursed through, for the reason given in @ref get_cbor_string
|
// level as required by RFC 8949, Section 3.2.3
|
||||||
std::size_t open = 0;
|
bool indefinite = false;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -15145,29 +15125,28 @@ class binary_reader
|
|||||||
|
|
||||||
if (current == 0x5F) // Binary data (indefinite length)
|
if (current == 0x5F) // Binary data (indefinite length)
|
||||||
{
|
{
|
||||||
++open;
|
if (JSON_HEDLEY_UNLIKELY(indefinite))
|
||||||
get();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// a break marker closes the innermost indefinite-length byte
|
|
||||||
// array; outside of one it falls through to the error below
|
|
||||||
if (open != 0 && current == 0xFF)
|
|
||||||
{
|
|
||||||
if (--open == 0)
|
|
||||||
{
|
{
|
||||||
return true;
|
return cbor_indefinite_string_error("binary array", "binary");
|
||||||
}
|
}
|
||||||
|
indefinite = true;
|
||||||
get();
|
get();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_binary_chunk(result)))
|
// a break marker closes the indefinite-length string; outside
|
||||||
|
// of one it falls through to the error below
|
||||||
|
if (indefinite && current == 0xFF)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!get_cbor_binary_chunk(result, indefinite)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (open == 0)
|
if (!indefinite)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -16997,12 +16976,6 @@ 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18459,7 +18432,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
if (JSON_HEDLEY_UNLIKELY(get_token() != 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(),
|
||||||
@@ -18622,7 +18595,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_expecting(token_type::value_separator))
|
if (get_token() == token_type::value_separator)
|
||||||
{
|
{
|
||||||
// parse a new value
|
// parse a new value
|
||||||
get_token();
|
get_token();
|
||||||
@@ -18662,7 +18635,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_expecting(token_type::value_separator))
|
if (get_token() == token_type::value_separator)
|
||||||
{
|
{
|
||||||
get_token();
|
get_token();
|
||||||
|
|
||||||
@@ -18683,7 +18656,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
if (JSON_HEDLEY_UNLIKELY(get_token() != 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(),
|
||||||
@@ -18727,13 +18700,6 @@ 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 ";
|
||||||
@@ -27470,70 +27436,6 @@ 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 //
|
||||||
////////////////
|
////////////////
|
||||||
@@ -27941,62 +27843,26 @@ private:
|
|||||||
return v.m_data.m_value.array->empty();
|
return v.m_data.m_value.array->empty();
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
return v.m_data.m_value.object->empty();
|
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:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The walk in destroy_container() may take the children of a
|
static basic_json& last_child(basic_json& v)
|
||||||
// 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)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
return v.m_data.m_value.array->back();
|
return v.m_data.m_value.array->back();
|
||||||
}
|
}
|
||||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||||
return walk_child_it(*v.m_data.m_value.object)->second;
|
return v.m_data.m_value.object->rbegin()->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
// removes walk_child(v) from a non-empty array/object v; this never
|
// removes the last child of a non-empty array/object v; this never
|
||||||
// allocates, and since it is only ever called when that child is a
|
// allocates, and since it is only ever called when that child is a
|
||||||
// scalar or an already-empty array/object, destroying it never
|
// scalar or an already-empty array/object, destroying it never
|
||||||
// recurses more than one level deep (see destroy() below)
|
// recurses more than one level deep (see destroy() below)
|
||||||
static void pop_walk_child(basic_json& v)
|
static void pop_last_child(basic_json& v)
|
||||||
{
|
{
|
||||||
if (v.m_data.m_type == value_t::array)
|
if (v.m_data.m_type == value_t::array)
|
||||||
{
|
{
|
||||||
@@ -28005,7 +27871,9 @@ private:
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
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));
|
// erase() needs a forward iterator, so std::prev(end()) is
|
||||||
|
// used here rather than rbegin() (see last_child() above)
|
||||||
|
v.m_data.m_value.object->erase(std::prev(v.m_data.m_value.object->end()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28076,17 +27944,14 @@ public:
|
|||||||
// bad_alloc, which would escape this noexcept destructor and
|
// bad_alloc, which would escape this noexcept destructor and
|
||||||
// terminate the program (#5135).
|
// terminate the program (#5135).
|
||||||
//
|
//
|
||||||
// Instead, walk down a chain of children (always the one
|
// Instead, walk down the "last child" chain, reversing links
|
||||||
// walk_child() picks), reversing links as we go: cur is the
|
// as we go: cur is the container currently being emptied,
|
||||||
// container currently being emptied, and prev is its parent
|
// and prev is its parent (value_t::null when there is none).
|
||||||
// (value_t::null when there is none). Each parent's
|
// Each parent's last child slot doubles as storage for that
|
||||||
// walk_child() slot doubles as storage for that parent's own
|
// parent's own parent link while we are below it, so no
|
||||||
// parent link while we are below it, so no extra memory is
|
// extra memory is needed. We only ever remove a child once
|
||||||
// needed; the parent is not modified meanwhile, so
|
// it is a scalar or an empty array/object, which neither
|
||||||
// walk_child() finds that same slot again on the way up. We
|
// allocates nor recurses more than one level deep.
|
||||||
// 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
|
// This json_value is not itself a basic_json, so the
|
||||||
// top-level container is first moved into a local stand-in
|
// top-level container is first moved into a local stand-in
|
||||||
@@ -28112,11 +27977,11 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ascend: detach the grandparent link from prev's
|
// ascend: detach the grandparent link from prev's
|
||||||
// walk_child() slot, drop that (now null) slot, free cur
|
// last slot, drop that (now null) slot, free cur
|
||||||
// (it is empty), then move up one level
|
// (it is empty), then move up one level
|
||||||
basic_json gp;
|
basic_json gp;
|
||||||
take(gp, walk_child(prev));
|
take(gp, last_child(prev));
|
||||||
pop_walk_child(prev);
|
pop_last_child(prev);
|
||||||
|
|
||||||
free_container(cur);
|
free_container(cur);
|
||||||
|
|
||||||
@@ -28125,21 +27990,21 @@ public:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
basic_json& cur_child_ref = walk_child(cur);
|
basic_json& cur_last_ref = last_child(cur);
|
||||||
|
|
||||||
if (has_no_children(cur_child_ref))
|
if (has_no_children(cur_last_ref))
|
||||||
{
|
{
|
||||||
// scalar, or already-empty array/object
|
// scalar, or already-empty array/object
|
||||||
pop_walk_child(cur);
|
pop_last_child(cur);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// descend into the non-empty child, reversing the
|
// descend into the non-empty last child, reversing the
|
||||||
// link: its slot takes over prev, and the child becomes
|
// link: its slot takes over prev, and the child becomes
|
||||||
// the new cur
|
// the new cur
|
||||||
basic_json tmp;
|
basic_json tmp;
|
||||||
take(tmp, cur_child_ref);
|
take(tmp, cur_last_ref);
|
||||||
take(cur_child_ref, prev);
|
take(cur_last_ref, prev);
|
||||||
take(prev, cur);
|
take(prev, cur);
|
||||||
take(cur, tmp);
|
take(cur, tmp);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,14 @@ 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::json::with_allocator_t<bad_allocator>;
|
using bad_json = nlohmann::basic_json<std::map,
|
||||||
|
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&);
|
||||||
@@ -122,7 +129,14 @@ 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::json::with_allocator_t<my_allocator>;
|
using my_json = nlohmann::basic_json<std::map,
|
||||||
|
std::vector,
|
||||||
|
std::string,
|
||||||
|
bool,
|
||||||
|
std::int64_t,
|
||||||
|
std::uint64_t,
|
||||||
|
double,
|
||||||
|
my_allocator>;
|
||||||
|
|
||||||
SECTION("class json_value")
|
SECTION("class json_value")
|
||||||
{
|
{
|
||||||
@@ -579,7 +593,14 @@ 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::json::with_allocator_t<allocator_no_forward>;
|
using bad_alloc_json = nlohmann::basic_json<std::map,
|
||||||
|
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();
|
||||||
|
|||||||
@@ -163,7 +163,16 @@ 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::json::with_string_t<alt_string>;
|
using alt_json = nlohmann::basic_json <
|
||||||
|
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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1286,32 +1286,6 @@ 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'};
|
||||||
|
|||||||
+23
-16
@@ -1699,7 +1699,7 @@ TEST_CASE("CBOR")
|
|||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0x61, 0X61})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0x61, 0X61})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 0X61})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 0X61})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F, 0x00})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR binary: expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x00", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F, 0x00})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR binary: expected length specification (0x40-0x5B); last byte: 0x00", json::parse_error&);
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x41})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x41})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
|
||||||
|
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x18}), true, false).is_discarded());
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x18}), true, false).is_discarded());
|
||||||
@@ -2305,22 +2305,21 @@ TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
|
|||||||
{
|
{
|
||||||
// Reading an indefinite-length string or byte array used to call itself
|
// Reading an indefinite-length string or byte array used to call itself
|
||||||
// once per chunk, so a payload of repeated 0x7F (or 0x5F) bytes exhausted
|
// once per chunk, so a payload of repeated 0x7F (or 0x5F) bytes exhausted
|
||||||
// the call stack before any of the input was rejected. The open levels are
|
// the call stack before any of the input was rejected. Nested indefinite
|
||||||
// counted now, and the levels below prove the reader still reads the same
|
// chunks are now rejected at the second byte, without recursing.
|
||||||
// values and reports the same errors at the same byte offsets.
|
|
||||||
json _;
|
json _;
|
||||||
|
|
||||||
SECTION("many open levels are reported, not crashed on")
|
SECTION("nested levels are rejected, not crashed on")
|
||||||
{
|
{
|
||||||
const std::vector<uint8_t> input(200000, 0x7F);
|
const std::vector<uint8_t> input(200000, 0x7F);
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 200001: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: indefinite-length string is not allowed inside indefinite-length string; last byte: 0x7F", json::parse_error&);
|
||||||
CHECK(json::from_cbor(input, true, false).is_discarded());
|
CHECK(json::from_cbor(input, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("many open levels are reported, not crashed on (binary)")
|
SECTION("nested levels are rejected, not crashed on (binary)")
|
||||||
{
|
{
|
||||||
const std::vector<uint8_t> input(200000, 0x5F);
|
const std::vector<uint8_t> input(200000, 0x5F);
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 200001: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR binary: indefinite-length binary array is not allowed inside indefinite-length binary array; last byte: 0x5F", json::parse_error&);
|
||||||
CHECK(json::from_cbor(input, true, false).is_discarded());
|
CHECK(json::from_cbor(input, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2328,22 +2327,22 @@ TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
|
|||||||
{
|
{
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0xFF})) == json(""));
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0xFF})) == json(""));
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x61, 0x61, 0xFF})) == json("a"));
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x61, 0x61, 0xFF})) == json("a"));
|
||||||
// nested indefinite-length strings are concatenated across levels
|
// empty and nonempty definite-length chunks concatenate in order
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x7F, 0x61, 0x61, 0xFF, 0x61, 0x62, 0xFF})) == json("ab"));
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x61, 'a', 0x60, 0x61, 'b', 0x61, 'c', 0xFF})) == json("abc"));
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x7F, 0x7F, 0x61, 0x7A, 0xFF, 0xFF, 0xFF})) == json("z"));
|
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0xA1, 0x7F, 0x61, 0x61, 0xFF, 0x01})) == json({{"a", 1}}));
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xA1, 0x7F, 0x61, 0x61, 0xFF, 0x01})) == json({{"a", 1}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("chunks are still concatenated (binary)")
|
SECTION("chunks are still concatenated (binary)")
|
||||||
{
|
{
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x41, 0x61, 0xFF})) == json::binary({0x61}));
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x41, 0x61, 0xFF})) == json::binary({0x61}));
|
||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x5F, 0x41, 0x61, 0xFF, 0x41, 0x62, 0xFF})) == json::binary({0x61, 0x62}));
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0xFF})) == json::binary({}));
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x41, 0x61, 0x40, 0x41, 0x62, 0x41, 0x63, 0xFF})) == json::binary({0x61, 0x62, 0x63}));
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("a chunk that is not a string is still rejected")
|
SECTION("a chunk that is not a string is still rejected")
|
||||||
{
|
{
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7F, 0x7F, 0x00})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x00", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7F, 0x00})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B); last byte: 0x00", json::parse_error&);
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F, 0x5F, 0x00})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR binary: expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x00", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F, 0x00})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR binary: expected length specification (0x40-0x5B); last byte: 0x00", json::parse_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("a break marker outside an indefinite-length string is not a string")
|
SECTION("a break marker outside an indefinite-length string is not a string")
|
||||||
@@ -2896,9 +2895,17 @@ TEST_CASE("examples from RFC 8949 Appendix A")
|
|||||||
{
|
{
|
||||||
const auto packed = utils::read_binary_file(TEST_DATA_DIRECTORY "/binary_data/cbor_binary.cbor");
|
const auto packed = utils::read_binary_file(TEST_DATA_DIRECTORY "/binary_data/cbor_binary.cbor");
|
||||||
json j;
|
json j;
|
||||||
CHECK_NOTHROW(j = json::from_cbor(packed));
|
// the fixture's tail contains nested indefinite-length byte strings.
|
||||||
|
CHECK_THROWS_WITH_AS(j = json::from_cbor(packed), "[json.exception.parse_error.113] parse error at byte 513: syntax error while parsing CBOR binary: indefinite-length binary array is not allowed inside indefinite-length binary array; last byte: 0x5F", json::parse_error&);
|
||||||
|
|
||||||
const auto expected = utils::read_binary_file(TEST_DATA_DIRECTORY "/binary_data/cbor_binary.out");
|
// keep the byte-for-byte decoding check for its valid prefix: the first
|
||||||
|
// 512 encoded bytes contain 468 payload bytes in definite-length chunks.
|
||||||
|
auto valid_prefix = packed;
|
||||||
|
valid_prefix.resize(512);
|
||||||
|
valid_prefix.push_back(0xFF);
|
||||||
|
auto expected = utils::read_binary_file(TEST_DATA_DIRECTORY "/binary_data/cbor_binary.out");
|
||||||
|
expected.resize(468);
|
||||||
|
CHECK_NOTHROW(j = json::from_cbor(valid_prefix));
|
||||||
CHECK(j == json::binary(expected));
|
CHECK(j == json::binary(expected));
|
||||||
|
|
||||||
// 0xd8
|
// 0xd8
|
||||||
|
|||||||
@@ -2317,58 +2317,6 @@ 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,7 +38,20 @@ class json_metadata
|
|||||||
};
|
};
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
using json_with_metadata = nlohmann::json::with_base_class_t<json_metadata<T>>;
|
using json_with_metadata =
|
||||||
|
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")
|
||||||
{
|
{
|
||||||
@@ -255,7 +268,19 @@ 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::json::with_base_class_t<visitor_adaptor>;
|
using json_with_visitor_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>,
|
||||||
|
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,9 +10,7 @@
|
|||||||
|
|
||||||
#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>
|
||||||
@@ -198,198 +196,6 @@ 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")
|
||||||
@@ -516,46 +322,3 @@ 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+8
-39
@@ -12,10 +12,8 @@
|
|||||||
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
|
||||||
{
|
{
|
||||||
@@ -93,9 +91,6 @@ 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;
|
||||||
|
|
||||||
@@ -112,7 +107,10 @@ 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
|
||||||
@@ -134,36 +132,7 @@ 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() == 19);
|
CHECK(hashes.size() == 21);
|
||||||
|
|
||||||
// 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>")
|
||||||
@@ -187,7 +156,10 @@ 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
|
||||||
@@ -209,10 +181,7 @@ 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() == 19);
|
CHECK(hashes.size() == 21);
|
||||||
|
|
||||||
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")
|
||||||
|
|||||||
@@ -920,4 +920,12 @@ TEST_CASE("regression test #5476 - array type without reserve()")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("issue #5317 - nested indefinite-length CBOR string chunks are rejected")
|
||||||
|
{
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0x7F, 0x7F, 0x61, 0x61, 0xFF, 0xFF})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: indefinite-length string is not allowed inside indefinite-length string; last byte: 0x7F", json::parse_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0x5F, 0x5F, 0x41, 0x61, 0xFF, 0xFF})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR binary: indefinite-length binary array is not allowed inside indefinite-length binary array; last byte: 0x5F", json::parse_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t>({0xA1, 0x7F, 0x7F, 0xFF, 0xFF, 0x01})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: indefinite-length string is not allowed inside indefinite-length string; last byte: 0x7F", json::parse_error&);
|
||||||
|
}
|
||||||
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
@@ -802,32 +802,6 @@ 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'};
|
||||||
|
|||||||
+3
-77
@@ -23,7 +23,6 @@ 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>
|
||||||
@@ -685,7 +684,8 @@ 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::json::with_json_serializer_t<pod_serializer>;
|
nlohmann::basic_json<std::map, std::vector, std::string, bool,
|
||||||
|
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::json::with_json_serializer_t<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>;
|
||||||
|
|
||||||
template <typename T, typename>
|
template <typename T, typename>
|
||||||
struct another_adl_serializer
|
struct another_adl_serializer
|
||||||
@@ -734,80 +734,6 @@ 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")
|
||||||
|
|||||||
Reference in new issue
Block a user