Compare commits

..
Author SHA1 Message Date
Niels Lohmann 2c65caa5bf Merge branch 'develop' into claude/issue-3885-3dcafd
Conflicts:
- Makefile: include.zip lists both develop's $(AMALGAMATED_LITERALS_FILE)
  and this PR's meson_options.txt
- meson.build: kept this PR's install_subdir(incdir / 'nlohmann'), which
  already installs develop's new json_literals.hpp in both header modes,
  in place of develop's per-file install_headers() lines

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:31:07 +02:00
Niels Lohmann 38528b4a38 Merge branch 'develop' into claude/issue-3885-3dcafd
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 20:58:21 +02:00
Niels Lohmann 9cbe62efb0 Fix the Meson CMake target for includedir or datadir outside the prefix
nlohmann_jsonTargets.cmake always prepended the prefix computed from the
file's location to includedir. With an absolute includedir outside the
prefix (as Nix passes for packages with a separate dev output), this gave
"<prefix>//abs/include"; with an absolute datadir outside the prefix, the
number of ".." was derived from the absolute path and the prefix resolved
to "/". In both cases find_package(nlohmann_json) failed with "Imported
target includes non-existent path". Use the absolute include directory in
these cases, as CMake's install(EXPORT) does, and check it in CI.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 18:23:27 +02:00
Niels Lohmann 471c7678cf Merge branch 'develop' into claude/issue-3885-3dcafd
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 17:50:55 +02:00
Niels Lohmann 4fabfb27f7 Merge branch 'develop' into claude/issue-3885-3dcafd
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 16:07:30 +02:00
Niels Lohmann 71b4eaf743 Document the Meson options and check them in CI
Describe the options of meson.build in FILES.md and in the Meson section
of the package manager documentation, which so far said that a Meson
installation always uses the default configuration.

The ci_meson_install job now also installs with non-default options and
checks that the headers of the multi-header version are installed, that
the definitions reach the pkg-config file and the CMake target, and that
find_package still works.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 14:51:27 +02:00
Niels Lohmann 2790175d79 Add the JSON_* definitions to the CMake pkg-config file
The installed pkg-config file only had the include path, so a
pkg-config user of a library installed with, for example,
-DJSON_Diagnostics=ON compiled without JSON_DIAGNOSTICS=1, unlike users
of the CMake target. Append the same definitions the target has. For the
default options, the file is unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 14:50:39 +02:00
Niels Lohmann cc9fafa0e1 Complete the Meson options and match CMake's compile definitions
Finish the Meson changes from #4452:

- Offer all eight options that change the CMake target, named like the
  CMake options without the JSON_ prefix. MultipleHeaders defaults to
  false so that existing Meson users keep the single header.
- Add a compile definition only for an option that differs from its
  default, exactly as target_compile_definitions does. Before, the
  GlobalUDLs and ImplicitConversions definitions were inverted and were
  also added in the default configuration.
- Keep the nlohmann_json_multiple_headers dependency that earlier
  versions of meson.build provided.
- Put the definitions into the pkg-config file and the Meson-generated
  nlohmann_jsonTargets.cmake, and install the pkg-config file into
  <datadir>/pkgconfig like CMake does.
- Ship meson_options.txt in include.zip, since meson.build now reads
  options and the zip is documented as a Meson subproject.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 14:50:10 +02:00
Dylan Baker 8d6d1264a7 Add meson information to FILES.md
Signed-off-by: Dylan Baker <dylan@pnwbakers.com>
2026-09-27 14:47:12 +02:00
Dylan Baker d75f9ab161 meson: add support for the ImplictConversions option
Signed-off-by: Dylan Baker <dylan@pnwbakers.com>
2026-09-27 14:47:04 +02:00
Dylan Baker c3774be4e2 meson: add support for the GlobalUDLs option
Signed-off-by: Dylan Baker <dylan@pnwbakers.com>
2026-09-27 14:47:03 +02:00
Dylan Baker 91365ca3b2 meson: handle single header and multiheader the same way CMake does
This uses a meson option (set in `meson_options.txt`) to control whether
multi-header or single-header setup is wanted.

Signed-off-by: Dylan Baker <dylan@pnwbakers.com>
2026-09-27 14:47:02 +02:00
Dylan Baker 605cc6c81d meson: set the C++ standard to C++11
Matching the CMake as closely as possible, as Meson doesn't have C++11
feature checks like CMAke does.

Signed-off-by: Dylan Baker <dylan@pnwbakers.com>
2026-09-27 14:47:01 +02:00
Dylan Baker f012b85d06 meson: use install_subdir for headers
This makes a single call to install the entire directory, and doesn't
need an update if any new headers are added. It also will simplify
bringing the Meson and CMake builds into allignment on how they handle
the multi-header vs single-header setups.

Signed-off-by: Dylan Baker <dylan@pnwbakers.com>
2026-09-27 14:46:56 +02:00
Dylan Baker 243b6e20e3 meson: use override_dependency() to set dependencies
This simplifies the use of json as a subproject.

Signed-off-by: Dylan Baker <dylan@pnwbakers.com>
2026-09-27 14:46:55 +02:00
Dylan Baker 0a0d5623be meson: set a minimum Meson version
Without a version set meson will give no developer warnings, including
deprecations. 0.64 was selected as it's quite old, it's the newest
version supported by muon (a pure C Meson implementation), and there's
nothing complicated going on here.

Signed-off-by: Dylan Baker <dylan@pnwbakers.com>
2026-09-27 14:46:55 +02:00
Dylan Baker 22c9ce3f21 meson: Indent code inside an if block
for better readability

Signed-off-by: Dylan Baker <dylan@pnwbakers.com>
2026-09-27 14:46:49 +02:00
Niels Lohmann 0d2240ad1e Install CMake package config files with Meson
A Meson installation only provided the headers and a pkg-config file, so
consumers using find_package(nlohmann_json) could not find the library.
Meson now also installs nlohmann_jsonConfig.cmake and
nlohmann_jsonConfigVersion.cmake from the existing templates, plus a
static nlohmann_jsonTargets.cmake that defines the header-only imported
target with a relocatable include path.

A new CI job installs with Meson and builds tests/cmake_import/project
against the result.

Closes #3885

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-27 14:25:10 +02:00
45 changed files with 436 additions and 1384 deletions
+40 -1
View File
@@ -31,6 +31,45 @@ jobs:
- name: Build
run: cmake --build build --target ci_test_gcc
ci_meson_install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
- name: Install Meson
run: pip install meson
- name: Install with Meson
run: |
meson setup build-meson --prefix=${{ github.workspace }}/install
meson install -C build-meson
- name: Use the installed package with find_package
run: |
cmake -S tests/cmake_import/project -B build-import -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install
cmake --build build-import
- name: Install with Meson and non-default options
run: |
meson setup build-meson-options --prefix=${{ github.workspace }}/install-options -DMultipleHeaders=true -DDiagnostics=true -DGlobalUDLs=false
meson install -C build-meson-options
- name: Check that the options reach the installed files
run: |
test -d install-options/include/nlohmann/detail
cflags=$(PKG_CONFIG_PATH=${{ github.workspace }}/install-options/share/pkgconfig pkg-config --cflags nlohmann_json)
echo "$cflags"
echo "$cflags" | grep -q -- '-DJSON_DIAGNOSTICS=1'
echo "$cflags" | grep -q -- '-DJSON_USE_GLOBAL_UDLS=0'
grep -q 'JSON_USE_GLOBAL_UDLS=0;JSON_DIAGNOSTICS=1' install-options/share/cmake/nlohmann_json/nlohmann_jsonTargets.cmake
cmake -S tests/cmake_import/project -B build-import-options -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-options
cmake --build build-import-options
- name: Install with Meson and the include directory outside the prefix
run: |
meson setup build-meson-split --prefix=${{ github.workspace }}/install-split --includedir=${{ github.workspace }}/install-split-dev/include
meson install -C build-meson-split
cmake -S tests/cmake_import/project -B build-import-split -DCMAKE_PREFIX_PATH=${{ github.workspace }}/install-split
cmake --build build-import-split
ci_infer:
runs-on: ubuntu-latest
steps:
@@ -100,7 +139,7 @@ jobs:
container: ubuntu:focal
strategy:
matrix:
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local]
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local]
steps:
- name: Install build-essential
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
+24 -7
View File
@@ -55,7 +55,6 @@ option(JSON_Diagnostic_Positions "Enable diagnostic positions." OFF)
option(JSON_GlobalUDLs "Place user-defined string literals in the global namespace." ON)
option(JSON_ImplicitConversions "Enable implicit conversions." ON)
option(JSON_DisableEnumSerialization "Disable default integer enum serialization." OFF)
option(JSON_DisableTupleReferenceConversion "Disable conversion from a one-element tuple of a JSON reference." OFF)
option(JSON_LegacyDiscardedValueComparison "Enable legacy discarded value comparison." OFF)
option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT})
option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON)
@@ -102,10 +101,6 @@ if (JSON_DisableEnumSerialization)
message(STATUS "Enum integer serialization is disabled (JSON_DISABLE_ENUM_SERIALIZATION=1)")
endif()
if (JSON_DisableTupleReferenceConversion)
message(STATUS "Tuple reference conversion is disabled (JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1)")
endif()
if (JSON_LegacyDiscardedValueComparison)
message(STATUS "Legacy discarded value comparison enabled (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1)")
endif()
@@ -148,7 +143,6 @@ target_compile_definitions(
$<$<NOT:$<BOOL:${JSON_GlobalUDLs}>>:JSON_USE_GLOBAL_UDLS=0>
$<$<NOT:$<BOOL:${JSON_ImplicitConversions}>>:JSON_USE_IMPLICIT_CONVERSIONS=0>
$<$<BOOL:${JSON_DisableEnumSerialization}>:JSON_DISABLE_ENUM_SERIALIZATION=1>
$<$<BOOL:${JSON_DisableTupleReferenceConversion}>:JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1>
$<$<BOOL:${JSON_Diagnostics}>:JSON_DIAGNOSTICS=1>
$<$<BOOL:${JSON_Diagnostic_Positions}>:JSON_DIAGNOSTIC_POSITIONS=1>
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
@@ -174,7 +168,30 @@ if (MSVC)
)
endif()
# Install a pkg-config file, so other tools can find this.
# Install a pkg-config file, so other tools can find this. It carries the same
# compile definitions as the target above.
set(NLOHMANN_JSON_PKGCONFIG_CFLAGS "")
if (NOT JSON_GlobalUDLs)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_GLOBAL_UDLS=0")
endif()
if (NOT JSON_ImplicitConversions)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_IMPLICIT_CONVERSIONS=0")
endif()
if (JSON_DisableEnumSerialization)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DISABLE_ENUM_SERIALIZATION=1")
endif()
if (JSON_Diagnostics)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DIAGNOSTICS=1")
endif()
if (JSON_Diagnostic_Positions)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_DIAGNOSTIC_POSITIONS=1")
endif()
if (JSON_LegacyDiscardedValueComparison)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1")
endif()
if (JSON_StrictNulHandling)
string(APPEND NLOHMANN_JSON_PKGCONFIG_CFLAGS " -DJSON_STRICT_NUL_HANDLING=1")
endif()
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/pkg-config.pc.in"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc"
+22 -2
View File
@@ -260,9 +260,29 @@ make BUILD.bazel
The "Check amalgamation" workflow fails if the file is out of date.
### `meson.build`
### `meson.build` and `meson_options.txt`
The build definition for the [Meson](https://mesonbuild.com) build system.
Meson build definitions suitable for use as a subproject ("wrap" in Meson terminology).
Projects wishing to use the wrap can execute:
```sh
meson wrap install nlohmann_json
```
Which allows Meson to build from source when a system provided dependency isn't available.
To build directly:
```sh
meson setup builddir
ninja -C builddir
```
`meson_options.txt` defines the options, which mirror the CMake options that change the library's target (for example,
`-DDiagnostics=true`). Meson requires this file next to `meson.build`, so it is also part of `include.zip`.
When installing, `meson.build` installs the headers, a pkg-config file, and the CMake package config files, so that
`find_package(nlohmann_json)` works. As Meson cannot generate `nlohmann_jsonTargets.cmake` itself, it is created from
the template `cmake/nlohmann_jsonTargets.cmake.in`, which is only used by Meson.
### `Package.swift`
+1 -1
View File
@@ -231,7 +231,7 @@ json.tar.xz:
# We use `-X` to make the resulting ZIP file reproducible, see
# <https://content.pivotal.io/blog/barriers-to-deterministic-reproducible-zip-files>.
include.zip: BUILD.bazel
zip -9 --recurse-paths -X include.zip $(SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) BUILD.bazel MODULE.bazel meson.build LICENSE.MIT
zip -9 --recurse-paths -X include.zip $(SRCS) $(AMALGAMATED_FILE) $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_LITERALS_FILE) BUILD.bazel MODULE.bazel meson.build meson_options.txt LICENSE.MIT
# Create the files for a release and add signatures and hashes.
release: include.zip json.tar.xz
-14
View File
@@ -276,20 +276,6 @@ add_custom_target(ci_test_disableenumserialization
COMMENT "Compile and test with enum serialization disabled"
)
###############################################################################
# Disable conversion from a one-element tuple of a JSON reference.
###############################################################################
add_custom_target(ci_test_disabletuplereferenceconversion
COMMAND ${CMAKE_COMMAND}
-DCMAKE_BUILD_TYPE=Debug -GNinja
-DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_DisableTupleReferenceConversion=ON
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_disabletuplereferenceconversion
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_disabletuplereferenceconversion
COMMAND cd ${PROJECT_BINARY_DIR}/build_disabletuplereferenceconversion && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
COMMENT "Compile and test with tuple reference conversion disabled"
)
###############################################################################
# Skip the multiple-inclusion library version check.
###############################################################################
+38
View File
@@ -0,0 +1,38 @@
# Imported target for installations made with Meson (see meson.build).
#
# CMake installations generate this file with install(EXPORT ...). Meson cannot
# do that, but as the library is header-only, the target only needs an include
# directory, the C++ standard, and the compile definitions of the options that
# differ from their defaults. Paths are computed relative to this file so that
# the installation can be relocated (e.g., into a sysroot), unless includedir or
# datadir is outside the prefix.
if(TARGET @PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@)
return()
endif()
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_DIR}/@NLOHMANN_JSON_CONFIG_TO_PREFIX@" ABSOLUTE)
add_library(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ INTERFACE IMPORTED)
set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "@NLOHMANN_JSON_INCLUDE_DIR@"
)
if(CMAKE_VERSION VERSION_LESS 3.8)
set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES
INTERFACE_COMPILE_FEATURES cxx_range_for
)
else()
set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES
INTERFACE_COMPILE_FEATURES cxx_std_11
)
endif()
set(_NLOHMANN_JSON_COMPILE_DEFINITIONS "@NLOHMANN_JSON_COMPILE_DEFINITIONS@")
if(_NLOHMANN_JSON_COMPILE_DEFINITIONS)
set_target_properties(@PROJECT_NAME@::@NLOHMANN_JSON_TARGET_NAME@ PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "${_NLOHMANN_JSON_COMPILE_DEFINITIONS}"
)
endif()
unset(_NLOHMANN_JSON_COMPILE_DEFINITIONS)
unset(_IMPORT_PREFIX)
+1 -1
View File
@@ -4,4 +4,4 @@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: @PROJECT_NAME@
Description: JSON for Modern C++
Version: @PROJECT_VERSION@
Cflags: -I${includedir}
Cflags: -I${includedir}@NLOHMANN_JSON_PKGCONFIG_CFLAGS@
-1
View File
@@ -210,7 +210,6 @@ INSERT INTO searchIndex(name, type, path) VALUES ('JSON_CATCH_USER', 'Macro', 'a
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'api/macros/json_diagnostics/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTIC_POSITIONS', 'Macro', 'api/macros/json_diagnostic_positions/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_ENUM_SERIALIZATION', 'Macro', 'api/macros/json_disable_enum_serialization/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_TUPLE_REFERENCE_CONVERSION', 'Macro', 'api/macros/json_disable_tuple_reference_conversion/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_11', 'Macro', 'api/macros/json_has_cpp_11/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_14', 'Macro', 'api/macros/json_has_cpp_11/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_17', 'Macro', 'api/macros/json_has_cpp_11/index.html');
@@ -159,8 +159,6 @@ basic_json(basic_json&& other) noexcept;
- `CompatibleType` is not `basic_json` (to avoid hijacking copy/move constructors),
- `CompatibleType` is not a different `basic_json` type (i.e. with different template arguments)
- `CompatibleType` is not a `basic_json` nested type (e.g., `json_pointer`, `iterator`, etc.)
- if [`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../macros/json_disable_tuple_reference_conversion.md) is defined
to `1`: `CompatibleType` is not a one-element `std::tuple` holding a reference to `basic_json`
- `json_serializer<U>` (with `U = uncvref_t<CompatibleType>`) has a `to_json(basic_json_t&, CompatibleType&&)`
method
+1 -4
View File
@@ -56,8 +56,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size`
is false.
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
not valid UTF-8
## Complexity
@@ -91,5 +89,4 @@ Linear in the size of the JSON value `j`.
## Version history
- Added in version 3.11.0.
- BJData version parameter (for draft3 binary encoding) added in version 3.12.0.
- Throwing `type_error.316` for a string or object key that is not valid UTF-8 added in version 3.13.0.
- BJData version parameter (for draft3 binary encoding) added in version 3.12.0.
@@ -46,8 +46,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [`out_of_range.415`](../../home/exceptions.md#jsonexceptionout_of_range415) if the subtype of a binary value
exceeds 255, the maximum of the BSON binary subtype; example:
`"subtype 70000 is too large for the BSON binary subtype (max 255)"`
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is
not valid UTF-8
## Complexity
@@ -84,5 +82,3 @@ pass before anything is written.
- Added in version 3.4.0.
- Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0.
- `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0.
- Throwing `type_error.316` for a string value or object key that is not valid UTF-8, detected before anything is
written, added in version 3.13.0.
@@ -35,11 +35,6 @@ The exact mapping and its limitations are described on a [dedicated page](../../
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
## Exceptions
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
not valid UTF-8
## Complexity
Linear in the size of the JSON value `j`.
@@ -73,4 +68,3 @@ Linear in the size of the JSON value `j`.
- Added in version 2.0.9.
- Compact representation of floating-point numbers added in version 3.8.0.
- Throwing `type_error.316` for a string or object key that is not valid UTF-8 added in version 3.13.0.
@@ -49,8 +49,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size`
is false.
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
not valid UTF-8
## Complexity
@@ -84,4 +82,3 @@ Linear in the size of the JSON value `j`.
## Version history
- Added in version 3.1.0.
- Throwing `type_error.316` for a string or object key that is not valid UTF-8 added in version 3.13.0.
-1
View File
@@ -53,7 +53,6 @@ header. See also the [macro overview page](../../features/macros.md).
- [**JSON_BRACE_INIT_COPY_SEMANTICS**](json_brace_init_copy_semantics.md) - opt in to copy/move semantics for single-element brace initialization
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
- [**JSON_DISABLE_TUPLE_REFERENCE_CONVERSION**](json_disable_tuple_reference_conversion.md) - switch off conversion from a one-element tuple of a JSON reference
- [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions
## Comparison behavior
@@ -1,114 +0,0 @@
# JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
```cpp
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION /* value */
```
When defined to `1`, a `basic_json` value can no longer be constructed from a one-element `std::tuple` whose element is
a reference to that `basic_json` type, such as `std::tuple<json&>`, `std::tuple<const json&>`, or `std::tuple<json&&>`.
These are the tuples created by `std::forward_as_tuple(j)`.
## Default definition
The default value is `0` (disabled — existing behavior is preserved).
```cpp
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
```
## Notes
!!! note "Background"
By default, `basic_json` can be constructed from any `std::tuple` whose elements can be converted to JSON; the result
is an array. This includes `std::tuple<json&>`, which becomes a one-element array.
`std::tuple` only converts another tuple element by element if its element type cannot be constructed from the whole
source tuple. Because `json` *can* be constructed from `std::tuple<json&>`, `std::tuple` instead converts the whole
tuple into a single `json` value. This has two surprising effects:
```cpp
json j = true;
// rejected by some standard libraries (e.g., libc++); with others, the
// reference binds to a temporary that is destroyed right away
std::tuple<const json&> t1(std::forward_as_tuple(j));
// compiles, but std::get<0>(t2) is [true], not true
std::tuple<json> t2(std::forward_as_tuple(j));
```
Enabling this macro removes the conversion, so both tuples are converted element by element: `std::get<0>(t1)`
refers to `j`, and `std::get<0>(t2)` is a copy of `j` (see [#2226](https://github.com/nlohmann/json/issues/2226)).
!!! warning "Opt-in only"
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no effect.
!!! note "Affected conversions"
Only one-element tuples holding a reference to the **same** `basic_json` type are affected. Constructing a JSON value
from them no longer compiles:
```cpp
json j = true;
json a = std::forward_as_tuple(j); // error with the macro enabled
json b = json::array({j}); // use this instead: [true]
```
Tuples holding a JSON value (`std::make_tuple(j)`), tuples with more than one element, and tuples holding references
to other types (including other `basic_json` specializations) are converted to arrays as before.
!!! hint "CMake option"
This behavior can also be controlled with the CMake option
[`JSON_DisableTupleReferenceConversion`](../../integration/cmake.md#json_disabletuplereferenceconversion)
(`OFF` by default) which defines `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION` accordingly.
## Examples
??? example "Default behavior (macro not defined)"
```cpp
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
json j = true;
std::tuple<json> t(std::forward_as_tuple(j));
// std::get<0>(t) is [true] -- the whole tuple was converted
}
```
??? example "Conversion disabled (macro defined to 1)"
```cpp
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
json j = true;
std::tuple<json> t(std::forward_as_tuple(j));
// std::get<0>(t) is true -- a copy of j
std::tuple<const json&> r(std::forward_as_tuple(j));
// std::get<0>(r) refers to j
}
```
## See also
- [**basic_json(CompatibleType&&)**](../basic_json/basic_json.md) - the affected constructor
- [:simple-cmake: JSON_DisableTupleReferenceConversion](../../integration/cmake.md#json_disabletuplereferenceconversion) -
CMake option to control the macro
## Version history
- Added in version 3.13.0.
@@ -63,12 +63,6 @@ The library uses the following mapping from JSON values types to BJData types ac
- strings with more than 18446744073709551615 bytes, i.e., $2^{64}-1$ bytes (theoretical)
!!! warning "UTF-8 validation of string values and object keys"
BJData strings must use UTF-8 encoding. `to_bjdata()` validates the bytes of every string value and object key
and throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8, so a
value with such a string cannot be serialized in the first place.
!!! info "Unused BJData markers"
The following markers are not used in the conversion:
@@ -214,13 +208,6 @@ The library maps BJData types to JSON value types as follows:
The mapping is **complete** in the sense that any BJData value can be converted to a JSON value.
!!! warning "UTF-8 validation of string values and object keys"
This library validates the bytes of every string value and object key at decode time and rejects ill-formed
UTF-8 with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with
`allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting value
is dumped.
!!! info "Round trips"
A value returned by [`from_bjdata`](../../api/basic_json/from_bjdata.md) can be serialized with
@@ -115,12 +115,8 @@ The library maps BSON record types to JSON value types as follows:
bytes of every such string at decode time and rejects ill-formed UTF-8 with a
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with `allow_exceptions`
set to `false`, a discarded value), rather than only failing later when the resulting value is dumped. Element
(key) names and `binary` values (type `0x05`) are unaffected and are never validated on read, since they are read
byte-by-byte as a C string, or are not required to hold text, respectively. `to_bson()` validates both string
values and element names and throws
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8 in either, so an
object with such a key or value cannot be produced in the first place, even though `from_bson()` would accept it
from another source.
(key) names and `binary` values (type `0x05`) are unaffected and are never validated, since they are read
byte-by-byte as a C string, or are not required to hold text, respectively.
??? example
@@ -197,9 +197,7 @@ The library maps CBOR types to JSON value types as follows:
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with
`allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting value is
dumped. Byte strings (major type 2) are unaffected and are never validated, since they are not required to hold
text. `to_cbor()` validates string values and object keys the same way and throws
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8, so a value with
such a string cannot be serialized in the first place.
text.
!!! warning "Tagged items"
@@ -153,15 +153,14 @@ The library maps MessagePack types to JSON value types as follows:
This applies to the [SAX interface](../parsing/sax_interface.md) as well, as the key is read before it is passed
on. Such input needs a general-purpose MessagePack library instead.
!!! warning "Ill-formed UTF-8 in string values"
!!! warning "UTF-8 validation of string values"
The MessagePack specification explicitly allows a `str` value (`fixstr`, `str 8`, `str 16`, `str 32`) to contain
a byte sequence that is not valid UTF-8, and expects a deserializer to hand the original bytes back unchanged.
This library follows that: `from_msgpack()` reads `str` bytes (object keys included) as-is, without validating
them, and `to_msgpack()` writes them back as-is, so such a value round-trips through `from_msgpack(to_msgpack(j))`
byte for byte. However, [`dump()`](../../api/basic_json/dump.md) still requires valid UTF-8 and throws
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for a value read this way, unless an
error handler is passed that replaces or ignores the ill-formed bytes.
The MessagePack specification requires `str` values (`fixstr`, `str 8`, `str 16`, `str 32`) to be valid UTF-8.
This library validates the bytes of every such string (object keys included) at decode time and rejects
ill-formed UTF-8 with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or,
with `allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting
value is dumped. `bin`/`ext`/`fixext` values are unaffected and are never validated, since they are not required
to hold text.
??? example
@@ -47,12 +47,6 @@ The library uses the following mapping from JSON values types to UBJSON types ac
- strings with more than 9223372036854775807 bytes (theoretical)
!!! warning "UTF-8 validation of string values and object keys"
UBJSON's required string encoding is UTF-8. `to_ubjson()` validates the bytes of every string value and object
key and throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) for ill-formed UTF-8, so
a value with such a string cannot be serialized in the first place.
!!! info "Unused UBJSON markers"
The following markers are not used in the conversion:
@@ -126,13 +120,6 @@ The library maps UBJSON types to JSON value types as follows:
The mapping is **complete** in the sense that any UBJSON value can be converted to a JSON value.
!!! warning "UTF-8 validation of string values and object keys"
This library validates the bytes of every string value and object key at decode time and rejects ill-formed
UTF-8 with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with
`allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting value
is dumped.
??? example
```cpp
-7
View File
@@ -83,13 +83,6 @@ When defined, default parse and serialize functions for enums are excluded and h
See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md).
## `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`
When defined to `1`, a JSON value can no longer be created from a one-element `std::tuple` holding a reference to a JSON
value, such as the result of `std::forward_as_tuple(j)`. This lets `std::tuple` convert such tuples element-wise.
See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md).
## `JSON_NO_AUTOMATIC_UDLS`
When defined, `<nlohmann/json.hpp>` does not include `<nlohmann/json_literals.hpp>` with the user-defined string literals
-6
View File
@@ -169,12 +169,6 @@ Enable position diagnostics by defining macro [`JSON_DIAGNOSTIC_POSITIONS`](../a
Disable default `enum` serialization by defining the macro
[`JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md). This option is `OFF` by default.
### `JSON_DisableTupleReferenceConversion`
Disable the conversion from a one-element `std::tuple` holding a reference to a JSON value by defining the macro
[`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md). This option is
`OFF` by default.
### `JSON_FastTests`
Skip expensive/slow test suites. This option is `OFF` by default. Depends on `JSON_BuildTests`.
@@ -121,10 +121,23 @@ meson wrap install nlohmann_json
Please see the Meson project for any issues regarding the packaging.
The provided `meson.build` can also be used as an alternative to CMake for installing `nlohmann_json` system-wide in
which case a pkg-config file is installed. To use it, have your build system require the `nlohmann_json`
pkg-config dependency. In Meson, it is preferred to use the
[`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a subproject fallback, rather than
using the subproject directly.
which case a pkg-config file and the CMake package config files are installed. To use it, have your build system require
the `nlohmann_json` pkg-config dependency, or use [`find_package(nlohmann_json)`](cmake.md#external) in CMake. In Meson,
it is preferred to use the [`dependency()`](https://mesonbuild.com/Reference-manual.html#dependency) object with a
subproject fallback, rather than using the subproject directly.
The options that change the library's configuration are available in Meson as well, named like the
[CMake options](cmake.md#cmake-options) without the `JSON_` prefix: `MultipleHeaders`, `GlobalUDLs`,
`ImplicitConversions`, `DisableEnumSerialization`, `Diagnostics`, `Diagnostic_Positions`,
`LegacyDiscardedValueComparison`, and `StrictNulHandling`. They have the same defaults as in CMake, except that
`MultipleHeaders` is `false`. Set them with `-D` when setting up the build, or with the subproject name as prefix when
the library is used as a subproject:
```shell
meson setup build -Dnlohmann_json:Diagnostics=true
```
The resulting compile definitions are part of the Meson dependency, the pkg-config file, and the CMake target.
??? example "Example: Wrap"
-1
View File
@@ -287,7 +287,6 @@ nav:
- 'JSON_DIAGNOSTICS': api/macros/json_diagnostics.md
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
- 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md
- 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md
- 'JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20': api/macros/json_has_cpp_11.md
- 'JSON_HAS_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md
- 'JSON_HAS_RANGES': api/macros/json_has_ranges.md
@@ -471,13 +471,11 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
j = { std::get<Idx>(t)... };
}
// A one-element braced list does not reliably wrap its element: with
// JSON_BRACE_INIT_COPY_SEMANTICS it copies it, which would serialize
// std::tuple<int>{5} as 5 rather than [5], and some compilers (e.g., Apple clang
// 15 and 16) copy an element that is itself a basic_json even without it, so
// std::tuple<json>{true} became true rather than [true]. Build what the default
// deduction builds instead: an object if the element is a [string, value] pair,
// a one-element array otherwise.
#if JSON_BRACE_INIT_COPY_SEMANTICS
// JSON_BRACE_INIT_COPY_SEMANTICS makes a one-element braced list copy its
// element instead of wrapping it, which would serialize std::tuple<int>{5} as 5
// rather than [5]. Build what the default deduction builds instead: an object
// if the element is a [string, value] pair, a one-element array otherwise.
template<typename BasicJsonType, typename Tuple>
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<0> /*unused*/)
{
@@ -495,6 +493,7 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
j = BasicJsonType::array({std::move(element)});
}
}
#endif
template<typename BasicJsonType, typename Tuple>
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/)
@@ -4111,16 +4111,12 @@ class binary_reader
return false;
}
// RFC 8949 (CBOR) §3.1 and the BSON/UBJSON specifications require
// text strings to be valid UTF-8; reject anything else right here so
// malformed input is caught at decode time instead of only surfacing
// later as a type_error.316 when the value is dumped (which would
// defeat allow_exceptions=false / strict discarding). The MessagePack
// specification explicitly allows a str object to contain an invalid
// byte sequence and expects deserializers to hand back the original
// bytes, so msgpack strings (and map keys, which go through this
// function as well) are exempt.
if (format != input_format_t::msgpack && JSON_HEDLEY_UNLIKELY(!is_valid_utf8(result, old_size)))
// RFC 8949 (CBOR) §3.1 and the MessagePack/BSON/UBJSON specifications
// all require text strings to be valid UTF-8; reject anything else
// right here so malformed input is caught at decode time instead of
// only surfacing later as a type_error.316 when the value is dumped
// (which would defeat allow_exceptions=false / strict discarding).
if (JSON_HEDLEY_UNLIKELY(!is_valid_utf8(result, old_size)))
{
return sax->parse_error(chars_read, get_token_string(),
parse_error::create(113, chars_read,
-4
View File
@@ -915,7 +915,3 @@ void templated_json_throw(ExceptionType exception)
#ifndef JSON_DISABLE_ENUM_SERIALIZATION
#define JSON_DISABLE_ENUM_SERIALIZATION 0
#endif
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
#endif
@@ -25,7 +25,6 @@
#undef JSON_INLINE_VARIABLE
#undef JSON_NO_UNIQUE_ADDRESS
#undef JSON_DISABLE_ENUM_SERIALIZATION
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
#ifndef JSON_TEST_KEEP_MACROS
#undef JSON_CATCH
@@ -636,18 +636,6 @@ template<typename BasicJsonType, typename CompatibleType>
struct is_compatible_type
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
// a one-element std::tuple holding a reference to BasicJsonType, as created by
// std::forward_as_tuple(j); see JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
template<typename BasicJsonType, typename T>
struct is_basic_json_reference_tuple : std::false_type {};
template<typename BasicJsonType, typename T>
struct is_basic_json_reference_tuple<BasicJsonType, std::tuple<T>>
{
static constexpr bool value =
std::is_reference<T>::value && std::is_same<uncvref_t<T>, BasicJsonType>::value;
};
template<typename BasicJsonType, typename CompatibleArrayType>
struct is_compatible_binary_type
{
@@ -116,8 +116,6 @@ class binary_writer
/*!
@param[in] j JSON value to serialize
@pre j.type() == value_t::object
@throw type_error.316 if a string value or an object key is not valid
UTF-8
*/
void write_bson(const BasicJsonType& j)
{
@@ -147,8 +145,6 @@ class binary_writer
/*!
@param[in] j JSON value to serialize
@throw type_error.316 if a string value or an object key is not valid
UTF-8
*/
void write_cbor(const BasicJsonType& j)
{
@@ -215,8 +211,6 @@ class binary_writer
case value_t::string:
{
check_utf8(*j.m_data.m_value.string, j);
// step 1: write control byte and the string length
write_cbor_head(0x60, j.m_data.m_value.string->size());
@@ -286,11 +280,6 @@ class binary_writer
// step 2: write each element
for (const auto& el : *j.m_data.m_value.object)
{
// el.first is checked here, against the object as
// diagnostics context, because write_cbor(el.first)
// converts it to a temporary basic_json that would be
// used as the context instead
check_utf8(el.first, j);
write_cbor(el.first);
write_cbor(el.second);
}
@@ -654,8 +643,6 @@ class binary_writer
@param[in] add_prefix whether prefixes need to be used for this value
@param[in] use_bjdata whether write in BJData format, default is false
@param[in] bjdata_version which BJData version to use, default is draft2
@throw type_error.316 if a string value or an object key is not valid
UTF-8
*/
void write_ubjson(const BasicJsonType& j, const bool use_count,
const bool use_type, const bool add_prefix = true,
@@ -705,8 +692,6 @@ class binary_writer
case value_t::string:
{
check_utf8(*j.m_data.m_value.string, j);
if (add_prefix)
{
oa.write_character(to_char_type('S'));
@@ -869,7 +854,6 @@ class binary_writer
for (const auto& el : *j.m_data.m_value.object)
{
check_utf8(el.first, j);
write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata);
oa.write_characters(
reinterpret_cast<const CharType*>(el.first.data()),
@@ -914,10 +898,6 @@ class binary_writer
/*!
@return The size of a BSON document entry header, including the id marker
and the entry name size (and its null-terminator).
@throw out_of_range.409 if @a name contains U+0000, before anything is
written
@throw type_error.316 if @a name is not valid UTF-8, before anything is
written
*/
static std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j)
{
@@ -927,8 +907,7 @@ class binary_writer
JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j));
}
check_utf8(name, j);
static_cast<void>(j);
return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;
}
@@ -984,21 +963,9 @@ class binary_writer
/*!
@return The size of the BSON-encoded string in @a value
@throw type_error.316 if @a value is not valid UTF-8, before anything is
written
@note The UTF-8 check is skipped if @a value is already too long for the
32-bit BSON length field (@ref to_bson_length rejects it later, once
the size of the whole document is known); this also keeps the check
from reading past a StringType that reports a size larger than what
it actually holds.
*/
static std::size_t calc_bson_string_size(const string_t& value, const BasicJsonType& j)
static std::size_t calc_bson_string_size(const string_t& value)
{
if (JSON_HEDLEY_LIKELY(value_in_range_of<std::int32_t>(value.size())))
{
check_utf8(value, j);
}
return sizeof(std::int32_t) + value.size() + 1ul;
}
@@ -1127,8 +1094,6 @@ class binary_writer
is neither an object nor an array
@throw out_of_range.415 if @a j is binary with a subtype that does not fit
into a byte, before anything is written
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
anything is written
*/
static std::size_t calc_bson_value_size(const BasicJsonType& j)
{
@@ -1150,7 +1115,7 @@ class binary_writer
return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);
case value_t::string:
return calc_bson_string_size(*j.m_data.m_value.string, j);
return calc_bson_string_size(*j.m_data.m_value.string);
case value_t::null:
return 0ul;
@@ -1263,8 +1228,6 @@ class binary_writer
written
@throw out_of_range.415 if a binary value's subtype does not fit into a
byte, before anything is written
@throw type_error.316 if a string value or a key is not valid UTF-8,
before anything is written
*/
static std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
{
@@ -2289,7 +2252,7 @@ class binary_writer
*/
void write_bon8_string(const string_t& s, bool& string_open, const BasicJsonType& context)
{
check_utf8(s, context);
check_bon8_utf8(s, context);
// a string that follows another string terminates it
if (string_open)
@@ -2319,7 +2282,7 @@ class binary_writer
@throw type_error.316 if @a s is not valid UTF-8; the message names the
first byte of the first invalid or incomplete sequence
*/
static void check_utf8(const string_t& s, const BasicJsonType& context)
static void check_bon8_utf8(const string_t& s, const BasicJsonType& context)
{
static_cast<void>(context); // only used when exceptions are enabled
const auto* data = reinterpret_cast<const unsigned char*>(s.data());
+48 -233
View File
@@ -906,11 +906,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/*!
@brief how many levels the operation going on in this thread has descended into
Copying a value, converting one from another specialization, and comparing
two values share this count. The library never nests one of them inside
another - none of them does either of the other two on the way - and where
user code nests them anyway, sharing the count only ends a descent sooner
than it had to, which costs a little speed and is never wrong.
Copying a value and comparing two values share this count. The library never
nests one inside the other - copying a value does not compare one, and
comparing two values does not copy them - and where user code nests them
anyway, sharing the count only ends a descent sooner than it had to, which
costs a little speed and is never wrong.
A byte is enough: the count never exceeds the limit by more than the single
level that notices the limit has been reached.
@@ -1267,222 +1267,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
copy_iteratively(src);
}
/*!
@brief convert the value @a val of another specialization into this null
value; @a val must be neither an object nor an array
Converting such a value never descends, so both ways of converting an
object or an array (@ref convert_structured) leave their elements of this
kind to the converting constructor, which leaves them to this.
*/
template<typename BasicJsonType>
void convert_leaf(const BasicJsonType& val)
{
using other_boolean_t = typename BasicJsonType::boolean_t;
using other_number_float_t = typename BasicJsonType::number_float_t;
using other_number_integer_t = typename BasicJsonType::number_integer_t;
using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
using other_string_t = typename BasicJsonType::string_t;
using other_binary_t = typename BasicJsonType::binary_t;
switch (val.type())
{
case value_t::boolean:
JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
break;
case value_t::number_float:
JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
break;
case value_t::number_integer:
JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
break;
case value_t::number_unsigned:
JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
break;
case value_t::string:
JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
break;
case value_t::binary:
JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>());
break;
case value_t::null:
// m_data.m_type is already value_t::null
break;
case value_t::discarded:
m_data.m_type = value_t::discarded;
break;
case value_t::object: // LCOV_EXCL_LINE
case value_t::array: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
}
}
/// scratch space for the converted elements of the arrays that
/// @ref convert_iteratively has yet to create
using convert_scratch_t = std::vector<basic_json, AllocatorType<basic_json>>;
/*!
@brief create the object or array @a val converted into this null value
Its converted elements are the last `val.size()` entries of @a elements (an
array) or of @a members (an object); they are moved into the container in
one go and then removed.
*/
template<typename BasicJsonType>
void convert_level(const BasicJsonType& val, convert_scratch_t& elements, copy_scratch_t& members)
{
if (val.is_object())
{
const auto first = members.end() - static_cast<typename copy_scratch_t::difference_type>(val.size());
m_data.m_value.object = create<object_t>(std::make_move_iterator(first),
std::make_move_iterator(members.end()));
// only now that the object exists may this stop being a null value
m_data.m_type = value_t::object;
members.erase(first, members.end());
}
else
{
const auto first = elements.end() - static_cast<typename convert_scratch_t::difference_type>(val.size());
m_data.m_value.array = create<array_t>(std::make_move_iterator(first),
std::make_move_iterator(elements.end()));
// only now that the array exists may this stop being a null value
m_data.m_type = value_t::array;
elements.erase(first, elements.end());
}
set_parents();
}
/*!
@brief convert the object or array @a val of another specialization into
this null value without recursing
The containers whose conversion has begun are kept on an explicit stack
rather than on the call stack. Unlike @ref copy_iteratively, this builds
every container from the bottom up: all its elements are converted first,
and the container is then created from them in one go, the way the range
constructor that converts the levels above the bound does. The two object
types need not enumerate their members in the same order, so the members
could not be paired up by position anyway, and building from a range keeps
what the range constructor does with keys that become equal on conversion.
Every value is complete before it is handed on, and a container gets its
type only once it exists, so whatever throws, every value left behind can
be destroyed.
*/
template<typename BasicJsonType>
void convert_iteratively(const BasicJsonType& val)
{
using other_const_iterator = typename BasicJsonType::const_iterator;
// the containers whose conversion has begun, innermost last, each with
// its element to convert next
std::vector<std::pair<const BasicJsonType*, other_const_iterator>> pending;
// the converted elements of the pending arrays and the converted
// members of the pending objects, those of the innermost one last
convert_scratch_t elements;
copy_scratch_t members;
pending.emplace_back(&val, val.cbegin());
for (;;)
{
const BasicJsonType& container = *pending.back().first;
// a copy, as descending below can reallocate pending; the
// iterator kept in pending is only advanced through pending.back()
const other_const_iterator next = pending.back().second;
if (next != container.cend())
{
if (next->is_structured())
{
// convert its elements first; next stays where it is until
// the converted container is handed back to this one
pending.emplace_back(&*next, next->cbegin());
continue;
}
// the converting constructor does not descend into this value
if (container.is_object())
{
members.emplace_back(next.key(), *next);
}
else
{
elements.emplace_back(*next);
}
++pending.back().second;
continue;
}
// all elements of the container are converted: create it
pending.pop_back();
if (pending.empty())
{
convert_level(container, elements, members);
return;
}
basic_json converted;
converted.convert_level(container, elements, members);
#if JSON_DIAGNOSTIC_POSITIONS
converted.start_position = container.start_pos();
converted.end_position = container.end_pos();
#endif
// hand it to the container it is an element of
if (pending.back().first->is_object())
{
members.emplace_back(pending.back().second.key(), std::move(converted));
}
else
{
elements.push_back(std::move(converted));
}
++pending.back().second;
}
}
/*!
@brief convert the object or array @a val of another specialization into
this null value
Converting a container converts its elements, so a value nested deeply
enough used to exhaust the call stack. The descent is bounded here as in
@ref copy_structured: the first `detail::recursion_depth_limit()` levels
are converted by the containers' range constructors, just as they always were,
and anything below that is converted without the call stack by
@ref convert_iteratively.
@sa https://github.com/nlohmann/json/issues/5650
*/
template<typename BasicJsonType>
void convert_structured(const BasicJsonType& val)
{
const nesting_depth_guard guard;
if (JSON_HEDLEY_LIKELY(guard.okay()))
{
// every element comes back to the converting constructor
if (val.is_object())
{
using other_object_t = typename BasicJsonType::object_t;
JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
}
else
{
using other_array_t = typename BasicJsonType::array_t;
JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
}
return;
}
convert_iteratively(val);
}
/// the result of comparing two values, including values that cannot be
/// ordered at all, such as a discarded value or a NaN
@@ -1812,12 +1596,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
template < typename CompatibleType,
typename U = detail::uncvref_t<CompatibleType>,
detail::enable_if_t <
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value
#if JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
// see https://github.com/nlohmann/json/issues/2226
&& !detail::is_basic_json_reference_tuple<basic_json_t, U>::value
#endif
, int > = 0 >
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
std::forward<CompatibleType>(val))))
@@ -1838,13 +1617,49 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
end_position(val.end_pos())
#endif
{
if (val.is_structured())
using other_boolean_t = typename BasicJsonType::boolean_t;
using other_number_float_t = typename BasicJsonType::number_float_t;
using other_number_integer_t = typename BasicJsonType::number_integer_t;
using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
using other_string_t = typename BasicJsonType::string_t;
using other_object_t = typename BasicJsonType::object_t;
using other_array_t = typename BasicJsonType::array_t;
using other_binary_t = typename BasicJsonType::binary_t;
switch (val.type())
{
convert_structured(val);
}
else
{
convert_leaf(val);
case value_t::boolean:
JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
break;
case value_t::number_float:
JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
break;
case value_t::number_integer:
JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
break;
case value_t::number_unsigned:
JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
break;
case value_t::string:
JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
break;
case value_t::object:
JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
break;
case value_t::array:
JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
break;
case value_t::binary:
JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>());
break;
case value_t::null:
*this = nullptr;
break;
case value_t::discarded:
m_data.m_type = value_t::discarded;
break;
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
}
JSON_ASSERT(m_data.m_type == val.type());
+96 -9
View File
@@ -2,24 +2,111 @@ project('nlohmann_json',
'cpp',
version : '3.12.0',
license : 'MIT',
meson_version : '>= 0.64',
default_options: ['cpp_std=c++11'],
)
if get_option('MultipleHeaders')
incdir = 'include'
else
incdir = 'single_include'
endif
# The same compile definitions as the CMake target (see target_compile_definitions
# in CMakeLists.txt): only an option that differs from its default adds one.
json_defines = []
if not get_option('GlobalUDLs')
json_defines += 'JSON_USE_GLOBAL_UDLS=0'
endif
if not get_option('ImplicitConversions')
json_defines += 'JSON_USE_IMPLICIT_CONVERSIONS=0'
endif
if get_option('DisableEnumSerialization')
json_defines += 'JSON_DISABLE_ENUM_SERIALIZATION=1'
endif
if get_option('Diagnostics')
json_defines += 'JSON_DIAGNOSTICS=1'
endif
if get_option('Diagnostic_Positions')
json_defines += 'JSON_DIAGNOSTIC_POSITIONS=1'
endif
if get_option('LegacyDiscardedValueComparison')
json_defines += 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1'
endif
if get_option('StrictNulHandling')
json_defines += 'JSON_STRICT_NUL_HANDLING=1'
endif
cpp_args = []
foreach define : json_defines
cpp_args += '-D' + define
endforeach
nlohmann_json_dep = declare_dependency(
include_directories: include_directories('single_include')
compile_args: cpp_args,
include_directories: include_directories(incdir)
)
meson.override_dependency('nlohmann_json', nlohmann_json_dep)
# The multi-header version under the name earlier versions of this file used
nlohmann_json_multiple_headers = declare_dependency(
compile_args: cpp_args,
include_directories: include_directories('include')
)
if not meson.is_subproject()
install_headers('single_include/nlohmann/json.hpp', subdir: 'nlohmann')
install_headers('single_include/nlohmann/json_fwd.hpp', subdir: 'nlohmann')
install_headers('single_include/nlohmann/json_literals.hpp', subdir: 'nlohmann')
install_subdir(
incdir / 'nlohmann',
install_dir: get_option('includedir'),
install_tag: 'devel',
)
pkgc = import('pkgconfig')
pkgc.generate(name: 'nlohmann_json',
version: meson.project_version(),
description: 'JSON for Modern C++'
)
pkgc = import('pkgconfig')
pkgc.generate(name: 'nlohmann_json',
version: meson.project_version(),
description: 'JSON for Modern C++',
extra_cflags: cpp_args,
install_dir: get_option('datadir') / 'pkgconfig',
)
# CMake package config files, so that find_package(nlohmann_json) works. The
# include directory is given relative to the config files, so that the
# installation can be relocated. This is not possible if includedir or datadir
# is an absolute path outside the prefix (e.g., with the separate outputs of
# Nix); then the absolute include directory is used, as CMake does.
fs = import('fs')
cmake_install_dir = get_option('datadir') / 'cmake' / meson.project_name()
cmake_to_prefix = []
if fs.is_absolute(get_option('includedir')) or fs.is_absolute(cmake_install_dir)
cmake_include_dir = (get_option('prefix') / get_option('includedir')).replace('\\', '/')
else
foreach component : cmake_install_dir.split('/')
cmake_to_prefix += '..'
endforeach
cmake_include_dir = '${_IMPORT_PREFIX}/' + get_option('includedir')
endif
cmake_conf = configuration_data()
cmake_conf.set('PROJECT_NAME', meson.project_name())
cmake_conf.set('PROJECT_VERSION', meson.project_version())
cmake_conf.set('PROJECT_VERSION_MAJOR', meson.project_version().split('.')[0])
cmake_conf.set('NLOHMANN_JSON_TARGET_NAME', meson.project_name())
cmake_conf.set('NLOHMANN_JSON_TARGETS_EXPORT_NAME', meson.project_name() + 'Targets')
cmake_conf.set('NLOHMANN_JSON_INCLUDE_DIR', cmake_include_dir)
cmake_conf.set('NLOHMANN_JSON_CONFIG_TO_PREFIX', '/'.join(cmake_to_prefix))
cmake_conf.set('NLOHMANN_JSON_COMPILE_DEFINITIONS', ';'.join(json_defines))
foreach cmake_file : [
['cmake/config.cmake.in', 'nlohmann_jsonConfig.cmake'],
['cmake/nlohmann_jsonConfigVersion.cmake.in', 'nlohmann_jsonConfigVersion.cmake'],
['cmake/nlohmann_jsonTargets.cmake.in', 'nlohmann_jsonTargets.cmake'],
]
configure_file(
input: cmake_file[0],
output: cmake_file[1],
configuration: cmake_conf,
format: 'cmake@',
install_dir: cmake_install_dir,
)
endforeach
endif
+48
View File
@@ -0,0 +1,48 @@
option(
'MultipleHeaders',
type: 'boolean',
value: false,
description: 'Use non-amalgamated version of the library',
)
option(
'GlobalUDLs',
type: 'boolean',
value: true,
description: 'Place user-defined string literals in the global namespace',
)
option(
'ImplicitConversions',
type: 'boolean',
value: true,
description: 'Enable implicit conversions',
)
option(
'DisableEnumSerialization',
type: 'boolean',
value: false,
description: 'Disable default integer enum serialization',
)
option(
'Diagnostics',
type: 'boolean',
value: false,
description: 'Use extended diagnostic messages',
)
option(
'Diagnostic_Positions',
type: 'boolean',
value: false,
description: 'Enable diagnostic positions',
)
option(
'LegacyDiscardedValueComparison',
type: 'boolean',
value: false,
description: 'Enable legacy discarded value comparison',
)
option(
'StrictNulHandling',
type: 'boolean',
value: false,
description: 'Enable strict NUL-byte handling',
)
+65 -309
View File
@@ -3329,10 +3329,6 @@ void templated_json_throw(ExceptionType exception)
#define JSON_DISABLE_ENUM_SERIALIZATION 0
#endif
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
#endif
#if JSON_HAS_THREE_WAY_COMPARISON
#include <compare> // partial_ordering
#endif
@@ -4648,18 +4644,6 @@ template<typename BasicJsonType, typename CompatibleType>
struct is_compatible_type
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
// a one-element std::tuple holding a reference to BasicJsonType, as created by
// std::forward_as_tuple(j); see JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
template<typename BasicJsonType, typename T>
struct is_basic_json_reference_tuple : std::false_type {};
template<typename BasicJsonType, typename T>
struct is_basic_json_reference_tuple<BasicJsonType, std::tuple<T>>
{
static constexpr bool value =
std::is_reference<T>::value && std::is_same<uncvref_t<T>, BasicJsonType>::value;
};
template<typename BasicJsonType, typename CompatibleArrayType>
struct is_compatible_binary_type
{
@@ -7018,13 +7002,11 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
j = { std::get<Idx>(t)... };
}
// A one-element braced list does not reliably wrap its element: with
// JSON_BRACE_INIT_COPY_SEMANTICS it copies it, which would serialize
// std::tuple<int>{5} as 5 rather than [5], and some compilers (e.g., Apple clang
// 15 and 16) copy an element that is itself a basic_json even without it, so
// std::tuple<json>{true} became true rather than [true]. Build what the default
// deduction builds instead: an object if the element is a [string, value] pair,
// a one-element array otherwise.
#if JSON_BRACE_INIT_COPY_SEMANTICS
// JSON_BRACE_INIT_COPY_SEMANTICS makes a one-element braced list copy its
// element instead of wrapping it, which would serialize std::tuple<int>{5} as 5
// rather than [5]. Build what the default deduction builds instead: an object
// if the element is a [string, value] pair, a one-element array otherwise.
template<typename BasicJsonType, typename Tuple>
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<0> /*unused*/)
{
@@ -7042,6 +7024,7 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
j = BasicJsonType::array({std::move(element)});
}
}
#endif
template<typename BasicJsonType, typename Tuple>
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/)
@@ -17664,16 +17647,12 @@ class binary_reader
return false;
}
// RFC 8949 (CBOR) §3.1 and the BSON/UBJSON specifications require
// text strings to be valid UTF-8; reject anything else right here so
// malformed input is caught at decode time instead of only surfacing
// later as a type_error.316 when the value is dumped (which would
// defeat allow_exceptions=false / strict discarding). The MessagePack
// specification explicitly allows a str object to contain an invalid
// byte sequence and expects deserializers to hand back the original
// bytes, so msgpack strings (and map keys, which go through this
// function as well) are exempt.
if (format != input_format_t::msgpack && JSON_HEDLEY_UNLIKELY(!is_valid_utf8(result, old_size)))
// RFC 8949 (CBOR) §3.1 and the MessagePack/BSON/UBJSON specifications
// all require text strings to be valid UTF-8; reject anything else
// right here so malformed input is caught at decode time instead of
// only surfacing later as a type_error.316 when the value is dumped
// (which would defeat allow_exceptions=false / strict discarding).
if (JSON_HEDLEY_UNLIKELY(!is_valid_utf8(result, old_size)))
{
return sax->parse_error(chars_read, get_token_string(),
parse_error::create(113, chars_read,
@@ -21244,8 +21223,6 @@ class binary_writer
/*!
@param[in] j JSON value to serialize
@pre j.type() == value_t::object
@throw type_error.316 if a string value or an object key is not valid
UTF-8
*/
void write_bson(const BasicJsonType& j)
{
@@ -21275,8 +21252,6 @@ class binary_writer
/*!
@param[in] j JSON value to serialize
@throw type_error.316 if a string value or an object key is not valid
UTF-8
*/
void write_cbor(const BasicJsonType& j)
{
@@ -21343,8 +21318,6 @@ class binary_writer
case value_t::string:
{
check_utf8(*j.m_data.m_value.string, j);
// step 1: write control byte and the string length
write_cbor_head(0x60, j.m_data.m_value.string->size());
@@ -21414,11 +21387,6 @@ class binary_writer
// step 2: write each element
for (const auto& el : *j.m_data.m_value.object)
{
// el.first is checked here, against the object as
// diagnostics context, because write_cbor(el.first)
// converts it to a temporary basic_json that would be
// used as the context instead
check_utf8(el.first, j);
write_cbor(el.first);
write_cbor(el.second);
}
@@ -21782,8 +21750,6 @@ class binary_writer
@param[in] add_prefix whether prefixes need to be used for this value
@param[in] use_bjdata whether write in BJData format, default is false
@param[in] bjdata_version which BJData version to use, default is draft2
@throw type_error.316 if a string value or an object key is not valid
UTF-8
*/
void write_ubjson(const BasicJsonType& j, const bool use_count,
const bool use_type, const bool add_prefix = true,
@@ -21833,8 +21799,6 @@ class binary_writer
case value_t::string:
{
check_utf8(*j.m_data.m_value.string, j);
if (add_prefix)
{
oa.write_character(to_char_type('S'));
@@ -21997,7 +21961,6 @@ class binary_writer
for (const auto& el : *j.m_data.m_value.object)
{
check_utf8(el.first, j);
write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata);
oa.write_characters(
reinterpret_cast<const CharType*>(el.first.data()),
@@ -22042,10 +22005,6 @@ class binary_writer
/*!
@return The size of a BSON document entry header, including the id marker
and the entry name size (and its null-terminator).
@throw out_of_range.409 if @a name contains U+0000, before anything is
written
@throw type_error.316 if @a name is not valid UTF-8, before anything is
written
*/
static std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j)
{
@@ -22055,8 +22014,7 @@ class binary_writer
JSON_THROW(out_of_range::create(409, concat("BSON key cannot contain code point U+0000 (at byte ", std::to_string(it), ")"), &j));
}
check_utf8(name, j);
static_cast<void>(j);
return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;
}
@@ -22112,21 +22070,9 @@ class binary_writer
/*!
@return The size of the BSON-encoded string in @a value
@throw type_error.316 if @a value is not valid UTF-8, before anything is
written
@note The UTF-8 check is skipped if @a value is already too long for the
32-bit BSON length field (@ref to_bson_length rejects it later, once
the size of the whole document is known); this also keeps the check
from reading past a StringType that reports a size larger than what
it actually holds.
*/
static std::size_t calc_bson_string_size(const string_t& value, const BasicJsonType& j)
static std::size_t calc_bson_string_size(const string_t& value)
{
if (JSON_HEDLEY_LIKELY(value_in_range_of<std::int32_t>(value.size())))
{
check_utf8(value, j);
}
return sizeof(std::int32_t) + value.size() + 1ul;
}
@@ -22255,8 +22201,6 @@ class binary_writer
is neither an object nor an array
@throw out_of_range.415 if @a j is binary with a subtype that does not fit
into a byte, before anything is written
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
anything is written
*/
static std::size_t calc_bson_value_size(const BasicJsonType& j)
{
@@ -22278,7 +22222,7 @@ class binary_writer
return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);
case value_t::string:
return calc_bson_string_size(*j.m_data.m_value.string, j);
return calc_bson_string_size(*j.m_data.m_value.string);
case value_t::null:
return 0ul;
@@ -22391,8 +22335,6 @@ class binary_writer
written
@throw out_of_range.415 if a binary value's subtype does not fit into a
byte, before anything is written
@throw type_error.316 if a string value or a key is not valid UTF-8,
before anything is written
*/
static std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
{
@@ -23417,7 +23359,7 @@ class binary_writer
*/
void write_bon8_string(const string_t& s, bool& string_open, const BasicJsonType& context)
{
check_utf8(s, context);
check_bon8_utf8(s, context);
// a string that follows another string terminates it
if (string_open)
@@ -23447,7 +23389,7 @@ class binary_writer
@throw type_error.316 if @a s is not valid UTF-8; the message names the
first byte of the first invalid or incomplete sequence
*/
static void check_utf8(const string_t& s, const BasicJsonType& context)
static void check_bon8_utf8(const string_t& s, const BasicJsonType& context)
{
static_cast<void>(context); // only used when exceptions are enabled
const auto* data = reinterpret_cast<const unsigned char*>(s.data());
@@ -27891,11 +27833,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/*!
@brief how many levels the operation going on in this thread has descended into
Copying a value, converting one from another specialization, and comparing
two values share this count. The library never nests one of them inside
another - none of them does either of the other two on the way - and where
user code nests them anyway, sharing the count only ends a descent sooner
than it had to, which costs a little speed and is never wrong.
Copying a value and comparing two values share this count. The library never
nests one inside the other - copying a value does not compare one, and
comparing two values does not copy them - and where user code nests them
anyway, sharing the count only ends a descent sooner than it had to, which
costs a little speed and is never wrong.
A byte is enough: the count never exceeds the limit by more than the single
level that notices the limit has been reached.
@@ -28252,222 +28194,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
copy_iteratively(src);
}
/*!
@brief convert the value @a val of another specialization into this null
value; @a val must be neither an object nor an array
Converting such a value never descends, so both ways of converting an
object or an array (@ref convert_structured) leave their elements of this
kind to the converting constructor, which leaves them to this.
*/
template<typename BasicJsonType>
void convert_leaf(const BasicJsonType& val)
{
using other_boolean_t = typename BasicJsonType::boolean_t;
using other_number_float_t = typename BasicJsonType::number_float_t;
using other_number_integer_t = typename BasicJsonType::number_integer_t;
using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
using other_string_t = typename BasicJsonType::string_t;
using other_binary_t = typename BasicJsonType::binary_t;
switch (val.type())
{
case value_t::boolean:
JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
break;
case value_t::number_float:
JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
break;
case value_t::number_integer:
JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
break;
case value_t::number_unsigned:
JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
break;
case value_t::string:
JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
break;
case value_t::binary:
JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>());
break;
case value_t::null:
// m_data.m_type is already value_t::null
break;
case value_t::discarded:
m_data.m_type = value_t::discarded;
break;
case value_t::object: // LCOV_EXCL_LINE
case value_t::array: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
}
}
/// scratch space for the converted elements of the arrays that
/// @ref convert_iteratively has yet to create
using convert_scratch_t = std::vector<basic_json, AllocatorType<basic_json>>;
/*!
@brief create the object or array @a val converted into this null value
Its converted elements are the last `val.size()` entries of @a elements (an
array) or of @a members (an object); they are moved into the container in
one go and then removed.
*/
template<typename BasicJsonType>
void convert_level(const BasicJsonType& val, convert_scratch_t& elements, copy_scratch_t& members)
{
if (val.is_object())
{
const auto first = members.end() - static_cast<typename copy_scratch_t::difference_type>(val.size());
m_data.m_value.object = create<object_t>(std::make_move_iterator(first),
std::make_move_iterator(members.end()));
// only now that the object exists may this stop being a null value
m_data.m_type = value_t::object;
members.erase(first, members.end());
}
else
{
const auto first = elements.end() - static_cast<typename convert_scratch_t::difference_type>(val.size());
m_data.m_value.array = create<array_t>(std::make_move_iterator(first),
std::make_move_iterator(elements.end()));
// only now that the array exists may this stop being a null value
m_data.m_type = value_t::array;
elements.erase(first, elements.end());
}
set_parents();
}
/*!
@brief convert the object or array @a val of another specialization into
this null value without recursing
The containers whose conversion has begun are kept on an explicit stack
rather than on the call stack. Unlike @ref copy_iteratively, this builds
every container from the bottom up: all its elements are converted first,
and the container is then created from them in one go, the way the range
constructor that converts the levels above the bound does. The two object
types need not enumerate their members in the same order, so the members
could not be paired up by position anyway, and building from a range keeps
what the range constructor does with keys that become equal on conversion.
Every value is complete before it is handed on, and a container gets its
type only once it exists, so whatever throws, every value left behind can
be destroyed.
*/
template<typename BasicJsonType>
void convert_iteratively(const BasicJsonType& val)
{
using other_const_iterator = typename BasicJsonType::const_iterator;
// the containers whose conversion has begun, innermost last, each with
// its element to convert next
std::vector<std::pair<const BasicJsonType*, other_const_iterator>> pending;
// the converted elements of the pending arrays and the converted
// members of the pending objects, those of the innermost one last
convert_scratch_t elements;
copy_scratch_t members;
pending.emplace_back(&val, val.cbegin());
for (;;)
{
const BasicJsonType& container = *pending.back().first;
// a copy, as descending below can reallocate pending; the
// iterator kept in pending is only advanced through pending.back()
const other_const_iterator next = pending.back().second;
if (next != container.cend())
{
if (next->is_structured())
{
// convert its elements first; next stays where it is until
// the converted container is handed back to this one
pending.emplace_back(&*next, next->cbegin());
continue;
}
// the converting constructor does not descend into this value
if (container.is_object())
{
members.emplace_back(next.key(), *next);
}
else
{
elements.emplace_back(*next);
}
++pending.back().second;
continue;
}
// all elements of the container are converted: create it
pending.pop_back();
if (pending.empty())
{
convert_level(container, elements, members);
return;
}
basic_json converted;
converted.convert_level(container, elements, members);
#if JSON_DIAGNOSTIC_POSITIONS
converted.start_position = container.start_pos();
converted.end_position = container.end_pos();
#endif
// hand it to the container it is an element of
if (pending.back().first->is_object())
{
members.emplace_back(pending.back().second.key(), std::move(converted));
}
else
{
elements.push_back(std::move(converted));
}
++pending.back().second;
}
}
/*!
@brief convert the object or array @a val of another specialization into
this null value
Converting a container converts its elements, so a value nested deeply
enough used to exhaust the call stack. The descent is bounded here as in
@ref copy_structured: the first `detail::recursion_depth_limit()` levels
are converted by the containers' range constructors, just as they always were,
and anything below that is converted without the call stack by
@ref convert_iteratively.
@sa https://github.com/nlohmann/json/issues/5650
*/
template<typename BasicJsonType>
void convert_structured(const BasicJsonType& val)
{
const nesting_depth_guard guard;
if (JSON_HEDLEY_LIKELY(guard.okay()))
{
// every element comes back to the converting constructor
if (val.is_object())
{
using other_object_t = typename BasicJsonType::object_t;
JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
}
else
{
using other_array_t = typename BasicJsonType::array_t;
JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
}
return;
}
convert_iteratively(val);
}
/// the result of comparing two values, including values that cannot be
/// ordered at all, such as a discarded value or a NaN
@@ -28797,12 +28523,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
template < typename CompatibleType,
typename U = detail::uncvref_t<CompatibleType>,
detail::enable_if_t <
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value
#if JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
// see https://github.com/nlohmann/json/issues/2226
&& !detail::is_basic_json_reference_tuple<basic_json_t, U>::value
#endif
, int > = 0 >
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
std::forward<CompatibleType>(val))))
@@ -28823,13 +28544,49 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
end_position(val.end_pos())
#endif
{
if (val.is_structured())
using other_boolean_t = typename BasicJsonType::boolean_t;
using other_number_float_t = typename BasicJsonType::number_float_t;
using other_number_integer_t = typename BasicJsonType::number_integer_t;
using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
using other_string_t = typename BasicJsonType::string_t;
using other_object_t = typename BasicJsonType::object_t;
using other_array_t = typename BasicJsonType::array_t;
using other_binary_t = typename BasicJsonType::binary_t;
switch (val.type())
{
convert_structured(val);
}
else
{
convert_leaf(val);
case value_t::boolean:
JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
break;
case value_t::number_float:
JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
break;
case value_t::number_integer:
JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
break;
case value_t::number_unsigned:
JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
break;
case value_t::string:
JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
break;
case value_t::object:
JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
break;
case value_t::array:
JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
break;
case value_t::binary:
JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>());
break;
case value_t::null:
*this = nullptr;
break;
case value_t::discarded:
m_data.m_type = value_t::discarded;
break;
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
}
JSON_ASSERT(m_data.m_type == val.type());
@@ -33815,7 +33572,6 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
#undef JSON_INLINE_VARIABLE
#undef JSON_NO_UNIQUE_ADDRESS
#undef JSON_DISABLE_ENUM_SERIALIZATION
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
#ifndef JSON_TEST_KEEP_MACROS
#undef JSON_CATCH
-71
View File
@@ -479,77 +479,6 @@ TEST_CASE("deep copy uses the provided allocator")
CHECK(copy == j);
}
namespace
{
// the number of constructions countdown_allocator lets happen, including the
// one that fails; 0 means none ever fails
std::size_t constructions_until_failure = 0;
template<class T>
struct countdown_allocator : std::allocator<T>
{
using std::allocator<T>::allocator;
template<class U, class... Args>
void construct(U* p, Args&& ... args)
{
if (constructions_until_failure != 0 && --constructions_until_failure == 0)
{
throw std::bad_alloc();
}
::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
}
template <class U>
struct rebind
{
using other = countdown_allocator<U>;
};
};
} // namespace
TEST_CASE("converting a deeply nested value from another specialization fails cleanly (#5650)")
{
using countdown_json = nlohmann::basic_json<std::map,
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
countdown_allocator>;
// deeper than the 128 levels the converting constructor descends into, so
// that failures land on both sides of the bound - or, built with
// JSON_NO_THREAD_LOCAL, all in the iterative conversion
json j = {1, "two", {{"three", 3}}};
for (std::size_t i = 0; i < 150; ++i)
{
j = json{{"a", json::array({j, "sibling"})}};
}
// Fail every construction in turn. Each failure has to reach the caller,
// and everything built until then has to be destroyed cleanly.
std::size_t failures = 0;
for (std::size_t n = 1;; ++n)
{
constructions_until_failure = n;
try
{
const countdown_json converted = j;
constructions_until_failure = 0;
CHECK(converted.dump() == j.dump());
break;
}
catch (const std::bad_alloc&)
{
++failures;
}
}
CHECK(failures > 0);
}
namespace
{
template<class T>
-24
View File
@@ -4025,30 +4025,6 @@ TEST_CASE("Universal Binary JSON Specification Examples 1")
CHECK(json::to_bjdata(j) == v);
CHECK(json::from_bjdata(v) == j);
}
SECTION("ill-formed UTF-8 (see #5651)")
{
// a string value whose bytes are not valid UTF-8 (0xC0 0xAE is an
// overlong encoding of '.') is rejected at decode time, matching
// every other kind of malformed binary input, and to_bjdata()
// rejects it as well, so a value it accepts can always be read
// back
const std::vector<uint8_t> v = {'S', 'i', 2, 0xc0, 0xae};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 5: syntax error while parsing BJData string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
CHECK(json::from_bjdata(v, true, false).is_discarded());
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xFF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
// a truncated multi-byte sequence
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xC3")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
// an encoded surrogate half (U+D800)
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xED\xA0\x80")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
// an overlong encoding of '.'
CHECK_THROWS_WITH_AS(json::to_bjdata(json("\xC0\xAF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
// an object key with ill-formed UTF-8 is rejected the same way
CHECK_THROWS_WITH_AS(json::to_bjdata(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
}
}
SECTION("Array Type")
-38
View File
@@ -151,44 +151,6 @@ TEST_CASE("BSON")
#endif
}
SECTION("ill-formed UTF-8 (see #5651)")
{
// a BSON document {"s": "\xC0\xAE"} (0xC0 0xAE is an overlong
// encoding of '.'); the reader rejects an ill-formed string value at
// decode time
const std::vector<uint8_t> v =
{
0x0F, 0x00, 0x00, 0x00, // document length
0x02, 's', 0x00, // type 0x02 (string), key "s"
0x03, 0x00, 0x00, 0x00, // string length (including null)
0xc0, 0xae, 0x00, // string content and its null terminator
0x00 // document terminator
};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.113] parse error at byte 13: syntax error while parsing BSON string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
CHECK(json::from_bson(v, true, false).is_discarded());
// to_bson() rejects the same kind of ill-formed string value, before
// any bytes reach the output adapter (the BSON document length
// prefix must be known up front, so nothing is written incrementally)
std::vector<std::uint8_t> out{0x42}; // a sentinel byte the writer must not touch
CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xFF"}}, nlohmann::detail::output_adapter<std::uint8_t>(out)), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
CHECK(out == std::vector<std::uint8_t> {0x42});
CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xFF"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
// a truncated multi-byte sequence
CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xC3"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
// an encoded surrogate half (U+D800)
CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xED\xA0\x80"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
// an overlong encoding of '.'
CHECK_THROWS_WITH_AS(json::to_bson(json{{"s", "\xC0\xAF"}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
// an object key with ill-formed UTF-8 is rejected as well; unlike
// the reader (which never validates element names), the writer
// checks both string values and object keys
CHECK_THROWS_WITH_AS(json::to_bson(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
}
SECTION("lengths exceeding INT32_MAX cannot be serialized to BSON")
{
// out_of_range.412 is thrown from a single shared helper
-19
View File
@@ -1896,25 +1896,6 @@ TEST_CASE("CBOR")
CHECK(json::from_cbor(json::to_cbor(j)) == j);
}
SECTION("to_cbor rejects ill-formed UTF-8 (see #5651)")
{
// to_cbor() must reject the same ill-formed strings from_cbor()
// rejects, so a value it accepts can always be read back
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xFF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
// a truncated multi-byte sequence
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xC3")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
// an encoded surrogate half (U+D800)
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xED\xA0\x80")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
// an overlong encoding of '.'
CHECK_THROWS_WITH_AS(json::to_cbor(json("\xC0\xAF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
// an object key with ill-formed UTF-8 is rejected the same way
CHECK_THROWS_WITH_AS(json::to_cbor(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
// binary values are not text and are unaffected
CHECK_NOTHROW(json::to_cbor(json::binary(std::vector<std::uint8_t>({0xFF}))));
}
SECTION("invalid UTF-8 in indefinite-length string")
{
json _;
-52
View File
@@ -141,58 +141,6 @@ TEST_CASE("Better diagnostics with positions")
check_objects(300);
}
SECTION("converting keeps the positions of nested values (#5650)")
{
// Values nested deeper than the converting constructor's descent bound
// are converted without the call stack, on a path that has to carry the
// positions of every value over itself. Objects and arrays take turns,
// and the innermost value is null, which used to lose its positions.
const auto check_conversion = [](std::size_t depth)
{
CAPTURE(depth)
std::string text;
std::string closing;
for (std::size_t i = 0; i < depth; ++i)
{
text += (i % 2 == 0) ? "[12, " : R"({"b":1, "a":)";
closing += (i % 2 == 0) ? ']' : '}';
}
text += "null";
text.append(closing.rbegin(), closing.rend());
const json original = json::parse(text);
const nlohmann::ordered_json converted = original;
const json* o = &original;
const nlohmann::ordered_json* c = &converted;
for (std::size_t level = 0; level <= depth; ++level)
{
CAPTURE(level)
REQUIRE(c->start_pos() == o->start_pos());
REQUIRE(c->end_pos() == o->end_pos());
if (level < depth)
{
// the number beside the value nested next
const json& o_number = o->is_object() ? o->at("b") : o->at(0);
const nlohmann::ordered_json& c_number = c->is_object() ? c->at("b") : c->at(0);
REQUIRE(c_number.start_pos() == o_number.start_pos());
REQUIRE(c_number.end_pos() == o_number.end_pos());
o = o->is_object() ? &o->at("a") : &o->at(1);
c = c->is_object() ? &c->at("a") : &c->at(1);
}
}
};
check_conversion(1);
check_conversion(127);
check_conversion(128);
check_conversion(129);
check_conversion(300);
}
SECTION("JSON patch add to primitive parent (#4292)")
{
// the JSON Patch "add" target /foo/bar/baz has a string parent
-30
View File
@@ -341,36 +341,6 @@ TEST_CASE("Regression tests for extended diagnostics")
}
}
SECTION("Regression test for issue #5650 - converting keeps the parents of nested values")
{
// A value nested deeper than the converting constructor's descent bound
// is converted without the call stack. Every container that path creates
// has to have the parents of its children set, or the JSON Pointer in the
// diagnostic is cut short. Objects and arrays take turns.
const std::size_t pairs = 150;
json j = "not a number";
std::string pointer;
for (std::size_t i = 0; i < pairs; ++i)
{
j = json{{"a", json::array({j})}};
pointer += "/a/0";
}
const nlohmann::ordered_json converted = j;
const nlohmann::ordered_json* inner = &converted;
for (std::size_t i = 0; i < pairs; ++i)
{
inner = &inner->at("a").at(0);
}
std::string const expected = "[json.exception.type_error.302] (" + pointer + ") type must be number, but is string";
int i = 0;
CHECK_THROWS_WITH_AS(i = inner->get<int>(), expected.c_str(), nlohmann::ordered_json::type_error);
CHECK(i == 0);
}
SECTION("Regression test for issue #5668 - wrong path for std::map/unordered_map with non-string keys")
{
// a map with non-string keys is read from an array of [key, value] arrays;
@@ -1,93 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
// This file tests the opt-in JSON_DISABLE_TUPLE_REFERENCE_CONVERSION, so it
// defines the macro itself rather than relying on a -D flag, and runs in every
// build.
#ifdef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
#endif
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1
#include <nlohmann/json.hpp>
using nlohmann::json;
using nlohmann::ordered_json;
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
// clang before 4 and GCC before 5 cannot create a std::tuple of basic_json
// references at all, with or without JSON_DISABLE_TUPLE_REFERENCE_CONVERSION:
// the tuple constructors make them instantiate basic_json's conversion operator
// for libstdc++'s internal tuple bases, which fails hard
#if (defined(__clang__) && __clang_major__ < 4) || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5)
#define SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
#endif
TEST_CASE("JSON_DISABLE_TUPLE_REFERENCE_CONVERSION")
{
SECTION("json is not constructible from a one-element tuple of a json reference")
{
CHECK_FALSE(std::is_constructible<json, std::tuple<json&>>::value);
CHECK_FALSE(std::is_constructible<json, std::tuple<const json&>>::value);
CHECK_FALSE(std::is_constructible < json, std::tuple < json && >>::value);
CHECK_FALSE(std::is_constructible<json, const std::tuple<json&>&>::value);
CHECK_FALSE(std::is_constructible<ordered_json, std::tuple<ordered_json&>>::value);
}
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
SECTION("issue #2226 - tuple<const json&> from tuple<json&> keeps the reference")
{
json j = true;
const std::tuple<const json&> tup(std::forward_as_tuple(j));
CHECK(&std::get<0>(tup) == &j);
}
SECTION("tuple<json> from tuple<json&> copies the element")
{
const json j = {{"key", "value"}};
const std::tuple<json> t1(std::forward_as_tuple(j));
CHECK(std::get<0>(t1) == j);
json j2 = "text";
const std::tuple<json> t2(std::forward_as_tuple(std::move(j2)));
CHECK(std::get<0>(t2) == "text");
}
#endif
SECTION("other tuple conversions are not affected")
{
const json j = true;
// one-element tuple holding a json value
CHECK(json(std::make_tuple(j)) == json::array({true}));
// tuples with more than one element, even when holding references
int i = 1;
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
CHECK(json(std::forward_as_tuple(i, j)) == json::array({1, true}));
CHECK(json(std::forward_as_tuple(j, j)) == json::array({true, true}));
#endif
// one-element tuples holding references to other types
std::string s = "text";
CHECK(json(std::forward_as_tuple(s)) == json::array({"text"}));
CHECK(json(std::forward_as_tuple(i)) == json::array({1}));
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
// a reference to a different basic_json specialization
ordered_json oj = true;
CHECK(json(std::forward_as_tuple(oj)) == json::array({true}));
#endif
}
}
-128
View File
@@ -13,7 +13,6 @@ using nlohmann::json;
#include <algorithm>
#include <string>
#include <vector>
TEST_CASE("tests on very large JSONs")
{
@@ -54,24 +53,6 @@ const json* innermost_value(const json& j, std::size_t& depth)
return current;
}
// The text of a value nested depth levels deep around the number 0. Level i is
// an array if pattern[i % pattern.size()] is '[', and otherwise an object with
// the single member "a", which every object type enumerates in the same order.
std::string nested_text(std::size_t depth, const std::string& pattern)
{
std::string text;
std::string closing;
for (std::size_t i = 0; i < depth; ++i)
{
const bool array = pattern[i % pattern.size()] == '[';
text += array ? "[" : "{\"a\":";
closing += array ? ']' : '}';
}
text += '0';
text.append(closing.rbegin(), closing.rend());
return text;
}
} // namespace
TEST_CASE("tests on deeply nested JSONs")
@@ -243,114 +224,5 @@ TEST_CASE("tests on deeply nested JSONs")
CHECK(*innermost_value(j, unused) == 0);
}
}
SECTION("issue #5650 - stack overflow converting between specializations")
{
const std::vector<std::string> patterns = {"[", "{", "[{"};
SECTION("json to ordered_json")
{
for (const auto& pattern : patterns)
{
CAPTURE(pattern);
const std::string text = nested_text(depth, pattern);
const json j = json::parse(text);
const nlohmann::ordered_json converted = j;
CHECK(converted.dump() == text);
}
}
SECTION("ordered_json to json")
{
for (const auto& pattern : patterns)
{
CAPTURE(pattern);
const std::string text = nested_text(depth, pattern);
const nlohmann::ordered_json o = nlohmann::ordered_json::parse(text);
const json converted = o;
CHECK(converted.dump() == text);
}
}
SECTION("get<ordered_json>()")
{
for (const auto& pattern : patterns)
{
CAPTURE(pattern);
const std::string text = nested_text(depth, pattern);
const json j = json::parse(text);
CHECK(j.get<nlohmann::ordered_json>().dump() == text);
}
}
SECTION("depths around the bound of the recursive descent")
{
for (std::size_t d = 1; d <= 300; ++d)
{
CAPTURE(d);
for (const auto& pattern : patterns)
{
CAPTURE(pattern);
const std::string text = nested_text(d, pattern);
const json j = json::parse(text);
const nlohmann::ordered_json converted = j;
CHECK(converted.dump() == text);
const json back = converted;
CHECK(back.dump() == text);
}
}
}
SECTION("values below the bound are converted as values above it")
{
// Bury a value below the bound, where it is converted without the
// call stack, and compare it with the same value converted on its
// own by the containers' range constructors. Its objects have
// members that the two object types enumerate in different orders.
const auto bury = [](nlohmann::ordered_json value)
{
for (std::size_t i = 0; i < 200; ++i)
{
value = nlohmann::ordered_json::array({std::move(value)});
}
return value;
};
const auto dig = [](const json & value)
{
const json* current = &value;
for (std::size_t i = 0; i < 200; ++i)
{
current = &current->at(0);
}
return current;
};
nlohmann::ordered_json value = nlohmann::ordered_json::object();
value["z"] = {1, -2, 3U, 4.5, true, nullptr, "six", nlohmann::ordered_json::binary({7, 8}, 9),
nlohmann::ordered_json::binary({10}), nlohmann::ordered_json::array(), nlohmann::ordered_json::object()
};
value["y"] = {{"x", {{"w", 1}, {"v", 2}}}, {"u", {3, {{"t", 4}, {"s", 5}}}}};
value["r"] = nlohmann::ordered_json::array({nlohmann::ordered_json(nlohmann::ordered_json::value_t::discarded)});
const json converted_above = value;
const json buried = bury(value);
const json& converted_below = *dig(buried);
CHECK(converted_below.dump() == converted_above.dump());
CHECK(converted_below.at("z").at(7).get_binary().subtype() == 9);
CHECK_FALSE(converted_below.at("z").at(8).get_binary().has_subtype());
CHECK(converted_below.at("r").at(0).is_discarded());
// a discarded value is never equal to anything, so compare the rest
value.erase("r");
const json without_discarded_above = value;
const json without_discarded_buried = bury(value);
CHECK(*dig(without_discarded_buried) == without_discarded_above);
}
}
}
+8 -28
View File
@@ -1614,39 +1614,19 @@ TEST_CASE("MessagePack")
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0x81})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing MessagePack string: unexpected end of input", json::parse_error&);
}
SECTION("ill-formed UTF-8 in string (see #5529, #5651)")
SECTION("invalid UTF-8 in string (see #5529)")
{
// the MessagePack specification explicitly allows a str object to
// contain a byte sequence that is not valid UTF-8 and expects a
// deserializer to hand the original bytes back unchanged; this
// library follows that, unlike CBOR/UBJSON/BJData/BSON, whose
// specifications require text strings to be valid UTF-8
// a fixstr of length 2 (0xA0 | 2) whose bytes are not valid UTF-8
// (0xC0 0xAE is an overlong encoding of '.') round-trips byte for
// byte as a string value
const std::vector<uint8_t> ill_formed_value = {0xa2, 0xc0, 0xae};
json j_value;
CHECK_NOTHROW(j_value = json::from_msgpack(ill_formed_value));
REQUIRE(j_value.is_string());
CHECK(j_value.get_ref<const json::string_t&>() == std::string("\xc0\xae"));
CHECK(json::from_msgpack(json::to_msgpack(j_value)) == j_value);
// dump() still requires valid UTF-8 and throws for such a value,
// unless an error handler that replaces or ignores the bytes is
// passed
CHECK_THROWS_AS(j_value.dump(), json::type_error&);
// the same bytes as an object key round-trip as well
const std::vector<uint8_t> ill_formed_key = {0x81, 0xa2, 0xc0, 0xae, 0x01};
json j_key;
CHECK_NOTHROW(j_key = json::from_msgpack(ill_formed_key));
REQUIRE(j_key.is_object());
CHECK(j_key.contains(std::string("\xc0\xae")));
CHECK(json::from_msgpack(json::to_msgpack(j_key)) == j_key);
// (0xC0 0xAE is an overlong encoding of '.') must be rejected at
// decode time, matching every other kind of malformed binary
// input, rather than only failing later when the resulting
// value is dumped
json _;
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xa2, 0xc0, 0xae})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing MessagePack string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
CHECK(json::from_msgpack(std::vector<uint8_t>({0xa2, 0xc0, 0xae}), true, false).is_discarded());
// a MessagePack bin8 blob with the very same bytes is NOT text
// and must still be accepted as-is
json _;
CHECK_NOTHROW(_ = json::from_msgpack(std::vector<uint8_t>({0xc4, 0x02, 0xc0, 0xae})));
CHECK(_ == json::binary(std::vector<std::uint8_t>({0xc0, 0xae})));
-28
View File
@@ -18,19 +18,6 @@
// for some reason including this after the json header leads to linker errors with VS 2017...
#include <locale>
// skip tests if JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1 (#2226)
#if defined(JSON_DISABLE_TUPLE_REFERENCE_CONVERSION) && (JSON_DISABLE_TUPLE_REFERENCE_CONVERSION == 1)
#define SKIP_TESTS_FOR_TUPLE_REFERENCE_CONVERSION
#endif
// clang before 4 and GCC before 5 cannot create a std::tuple of basic_json
// references at all, with or without JSON_DISABLE_TUPLE_REFERENCE_CONVERSION:
// the tuple constructors make them instantiate basic_json's conversion operator
// for libstdc++'s internal tuple bases, which fails hard
#if (defined(__clang__) && __clang_major__ < 4) || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5)
#define SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
#endif
#define JSON_TESTS_PRIVATE
#include <nlohmann/json.hpp>
using json = nlohmann::json;
@@ -41,7 +28,6 @@ using ordered_json = nlohmann::ordered_json;
#include <cstdio>
#include <list>
#include <tuple>
#include <type_traits>
#include <utility>
@@ -556,20 +542,6 @@ TEST_CASE("regression tests 2")
)));
}
#ifndef SKIP_TESTS_FOR_TUPLE_REFERENCE_CONVERSION
SECTION("issue #2226 - std::tuple dangling reference - implicit conversion")
{
// by default, a one-element tuple holding a json reference converts to
// a one-element array; JSON_DISABLE_TUPLE_REFERENCE_CONVERSION removes
// this conversion (see unit-disable-tuple-reference-conversion.cpp)
const json j = true;
CHECK(std::is_constructible<json, std::tuple<const json&>>::value);
#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES
CHECK(json(std::forward_as_tuple(j)) == json::array({true}));
#endif
}
#endif
SECTION("PR #2181 - regression bug with lvalue")
{
// see https://github.com/nlohmann/json/pull/2181#issuecomment-653326060
-24
View File
@@ -2580,30 +2580,6 @@ TEST_CASE("Universal Binary JSON Specification Examples 1")
CHECK(json::to_ubjson(j) == v);
CHECK(json::from_ubjson(v) == j);
}
SECTION("ill-formed UTF-8 (see #5651)")
{
// a string value whose bytes are not valid UTF-8 (0xC0 0xAE is an
// overlong encoding of '.') is rejected at decode time, matching
// every other kind of malformed binary input, and to_ubjson()
// rejects it as well, so a value it accepts can always be read
// back
const std::vector<uint8_t> v = {'S', 'i', 2, 0xc0, 0xae};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.113] parse error at byte 5: syntax error while parsing UBJSON string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
CHECK(json::from_ubjson(v, true, false).is_discarded());
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xFF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
// a truncated multi-byte sequence
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xC3")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC3", json::type_error&);
// an encoded surrogate half (U+D800)
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xED\xA0\x80")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xED", json::type_error&);
// an overlong encoding of '.'
CHECK_THROWS_WITH_AS(json::to_ubjson(json("\xC0\xAF")), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xC0", json::type_error&);
// an object key with ill-formed UTF-8 is rejected the same way
CHECK_THROWS_WITH_AS(json::to_ubjson(json{{"\xFF", 1}}), "[json.exception.type_error.316] invalid UTF-8 byte at index 0: 0xFF", json::type_error&);
}
}
SECTION("Array Type")