Compare commits

..
Author SHA1 Message Date
Niels Lohmann b87a618151 Pin Google Benchmark to release 1.9.5
The benchmarks fetched Google Benchmark's main branch, so two builds on
different days could measure with different library code, and CMake 3.30
and later warn that the single-argument FetchContent_Populate() is
deprecated. Fetch the 1.9.5 release archive, verified by its SHA-256,
with FetchContent_MakeAvailable() instead. That needs CMake 3.14; Google
Benchmark itself already needed 3.13.

Its -Werror is switched off, so a newer compiler's new warnings cannot
break the pinned release, and its install rules are no longer added.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-24 09:48:18 +02:00
Niels Lohmann 67ae3fd85b Point ci_benchmarks at tests/benchmarks
The target has configured ${PROJECT_SOURCE_DIR}/benchmarks since it was
added in #2561, but the benchmarks live in tests/benchmarks.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-24 07:10:37 +02:00
Niels Lohmann 71fe9f0b96 Document the benchmarks, and make them build and compare versions again
The benchmark project hasn't configured since #4793: download_test_data.cmake
compiles cmake/detect_libcpp_version.cpp relative to CMAKE_SOURCE_DIR, which
is tests/benchmarks when that is the top-level project, so try_run fails and
so does `make run_benchmarks`. The path is now relative to the module itself,
which is the same file for the main build.

The Dump benchmark discarded dump()'s result, which is [[nodiscard]] by now;
it warned, and left the optimizer free to shorten the loop. The result is
now kept with benchmark::DoNotOptimize.

A new cache variable, JSON_BENCHMARK_INCLUDE_DIR, names the directory holding
the nlohmann/json.hpp to benchmark (single_include by default, as before),
so the same benchmarks can be built against two versions and compared.

tests/benchmarks/README.md documents what is measured, how to build and run
the benchmarks, how to read the output, and how to compare two versions with
Google Benchmark's compare.py; it recommends doing so by hand before a
release rather than in CI.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-24 07:07:59 +02:00
61 changed files with 435 additions and 3581 deletions
-9
View File
@@ -158,15 +158,6 @@ make amalgamate
Running `make amalgamate` will also apply automatic formatting to the source files using
[`Artistic Style`](https://astyle.sourceforge.net/). This formatting may modify your source files in-place. Be certain to review and commit any changes to avoid unintended formatting diffs in commits.
If you add, rename, or remove a header in `include/nlohmann`, also regenerate the header list in
[`BUILD.bazel`](https://github.com/nlohmann/json/blob/develop/BUILD.bazel) (requires CMake) by executing:
```shell
make BUILD.bazel
```
The amalgamation check in CI fails if any of these generated files is out of date.
## Recommended documentation
- The library’s [README file](https://github.com/nlohmann/json/blob/master/README.md) is an excellent starting point to
-21
View File
@@ -29,27 +29,6 @@ labels:
files:
- ".github/external_ci/.*"
- label: "CI"
files:
- ".github/(dependabot|labeler)\\.yml"
- label: "aspect: binary formats"
files:
- "include/nlohmann/detail/input/binary_reader\\.hpp"
- "include/nlohmann/detail/output/binary_writer\\.hpp"
- "tests/src/unit-(bson|cbor|msgpack|ubjson|bjdata|binary_formats)"
- "tests/src/fuzzer-parse_(bson|cbor|msgpack|ubjson|bjdata)"
- "docs/mkdocs/docs/features/binary_formats/"
- "docs/mkdocs/docs/(api/basic_json|examples)/(to|from)_(bson|cbor|msgpack|ubjson|bjdata)"
- label: "aspect: binary formats"
title: "(?i)(bson|cbor|msgpack|messagepack|ubjson|bjdata|binary format)"
- label: "python"
files:
- "\\.py$"
- "requirements[^/]*\\.txt$"
- label: "S"
size-below: 10
- label: "M"
+2 -5
View File
@@ -57,16 +57,13 @@ jobs:
python3 -mvenv venv
venv/bin/pip3 install -r $MAIN_DIR/tools/astyle/requirements.txt
- name: Regenerate amalgamation, formatting, and BUILD.bazel
- name: Regenerate amalgamation and formatting
run: |
cd $MAIN_DIR
python3 $TOOL_DIR/amalgamate.py -c $TOOL_DIR/config_json.json -s .
python3 $TOOL_DIR/amalgamate.py -c $TOOL_DIR/config_json_fwd.json -s .
# the header list of the Bazel "json" target must match the files in include/
cmake -P cmake/scripts/gen_bazel_build_file.cmake
${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet \
$INCLUDE_DIR/json.hpp $INCLUDE_DIR/json_fwd.hpp
@@ -90,7 +87,7 @@ jobs:
mkdir -p ${{ github.workspace }}/patch
git diff --patch --no-color > ${{ github.workspace }}/patch/amalgamation.patch
if [ -s ${{ github.workspace }}/patch/amalgamation.patch ]; then
echo "The source code has not been amalgamated/formatted correctly or BUILD.bazel is out of date. Diff:"
echo "The source code has not been amalgamated/formatted correctly. Diff:"
cat ${{ github.workspace }}/patch/amalgamation.patch
echo "has_diff=true" >> "$GITHUB_OUTPUT"
else
@@ -95,13 +95,13 @@ jobs:
issue_number: issue_number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '## 🔴 Amalgamation check failed! 🔴\nThe source code has not been amalgamated and/or formatted correctly, or `BUILD.bazel` is out of date.'
body: '## 🔴 Amalgamation check failed! 🔴\nThe source code has not been amalgamated and/or formatted correctly.'
+ (hasPatch ? '\n\n📎 A ready-to-apply patch is attached to the [failed workflow run](' + runUrl + ') as the `amalgamation-patch` artifact.'
+ ' Download it, then apply it locally from the repository root with:'
+ '\n\n```shell\ngit apply amalgamation.patch\n```\n\n'
+ 'This does not require installing astyle yourself.'
: '')
+ (first ? '\n\n@' + author + ' Please read and follow the [Contribution Guidelines]'
+ '(https://github.com/nlohmann/json/blob/develop/.github/CONTRIBUTING.md#amalgamate-the-source-code).'
+ '(https://github.com/nlohmann/json/blob/develop/.github/CONTRIBUTING.md#files-to-change).'
: '')
})
+1 -1
View File
@@ -100,7 +100,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_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]
steps:
- name: Install build-essential
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
-4
View File
@@ -158,10 +158,6 @@ jobs:
# to fit: IMAGE_REL_AMD64_SECREL against `.debug_line'" because the
# MinGW linker cannot relocate the debug sections this test produces.
# The tests are only built and run here, so the debug info is not used.
# Do not add -O1 here to shrink the objects further: it does make them
# link, but the binaries clang 11.0.1 and clang 18.1.8 then produce crash
# before doctest prints its first line - 39 of 102 tests on clang 18.
# Keep the objects small by splitting the test files instead.
- name: Run CMake
run: cmake -S . -B build ^
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^
-4
View File
@@ -30,10 +30,8 @@ cc_library(
"include/nlohmann/detail/input/input_adapters.hpp",
"include/nlohmann/detail/input/json_sax.hpp",
"include/nlohmann/detail/input/lexer.hpp",
"include/nlohmann/detail/input/number_parse.hpp",
"include/nlohmann/detail/input/parser.hpp",
"include/nlohmann/detail/input/position_t.hpp",
"include/nlohmann/detail/input/string_scan.hpp",
"include/nlohmann/detail/iterators/internal_iterator.hpp",
"include/nlohmann/detail/iterators/iter_impl.hpp",
"include/nlohmann/detail/iterators/iteration_proxy.hpp",
@@ -51,14 +49,12 @@ cc_library(
"include/nlohmann/detail/meta/detected.hpp",
"include/nlohmann/detail/meta/identity_tag.hpp",
"include/nlohmann/detail/meta/is_sax.hpp",
"include/nlohmann/detail/meta/logic.hpp",
"include/nlohmann/detail/meta/std_fs.hpp",
"include/nlohmann/detail/meta/type_traits.hpp",
"include/nlohmann/detail/meta/void_t.hpp",
"include/nlohmann/detail/output/binary_writer.hpp",
"include/nlohmann/detail/output/output_adapters.hpp",
"include/nlohmann/detail/output/serializer.hpp",
"include/nlohmann/detail/recursion_depth_limit.hpp",
"include/nlohmann/detail/string_concat.hpp",
"include/nlohmann/detail/string_escape.hpp",
"include/nlohmann/detail/string_utils.hpp",
+1 -5
View File
@@ -250,16 +250,12 @@ Further documentation:
### `BUILD.bazel`
The build definition for [Bazel](https://bazel.build). The file is generated by
`cmake/scripts/gen_bazel_build_file.cmake`, which derives the header list from the files in `include`; change the
script rather than editing the file by hand. The file can be updated by calling
The file can be updated by calling
```shell
make BUILD.bazel
```
The "Check amalgamation" workflow fails if the file is out of date.
### `meson.build`
The build definition for the [Meson](https://mesonbuild.com) build system.
+3 -9
View File
@@ -1,4 +1,4 @@
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef
##########################################################################
# configuration
@@ -30,9 +30,8 @@ AMALGAMATED_FWD_FILE=single_include/nlohmann/json_fwd.hpp
# main target
all:
@echo "amalgamate - amalgamate files single_include/nlohmann/json{,_fwd}.hpp from the include/nlohmann sources"
@echo "BUILD.bazel - regenerate the Bazel BUILD file from the include/nlohmann sources"
@echo "ChangeLog.md - generate ChangeLog file"
@echo "check-amalgamation - check whether sources have been amalgamated and BUILD.bazel is up to date"
@echo "check-amalgamation - check whether sources have been amalgamated"
@echo "clean - remove built files"
@echo "doctest - compile example files and check their output"
@echo "fuzz_testing - prepare fuzz testing of the JSON parser"
@@ -173,13 +172,8 @@ check-amalgamation:
@diff $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_FWD_FILE)~ || (echo "===================================================================\n Amalgamation required! Please read the contribution guidelines\n in file .github/CONTRIBUTING.md.\n===================================================================" ; mv $(AMALGAMATED_FWD_FILE)~ $(AMALGAMATED_FWD_FILE) ; false)
@mv $(AMALGAMATED_FILE)~ $(AMALGAMATED_FILE)
@mv $(AMALGAMATED_FWD_FILE)~ $(AMALGAMATED_FWD_FILE)
@mv BUILD.bazel BUILD.bazel~
@$(MAKE) BUILD.bazel
@diff BUILD.bazel BUILD.bazel~ || (echo "===================================================================\n BUILD.bazel is out of date! Please run 'make BUILD.bazel'.\n===================================================================" ; mv BUILD.bazel~ BUILD.bazel ; false)
@mv BUILD.bazel~ BUILD.bazel
# generate the Bazel BUILD file; phony, because a removed header would not trigger a rebuild
BUILD.bazel:
BUILD.bazel: $(SRCS)
cmake -P cmake/scripts/gen_bazel_build_file.cmake
##########################################################################
+16 -20
View File
@@ -230,6 +230,21 @@ add_custom_target(ci_test_simdutf
COMMENT "Compile and test with simdutf UTF-8 validation enabled"
)
###############################################################################
# Enable brace-init copy semantics.
###############################################################################
add_custom_target(ci_test_brace_init_copy_semantics
COMMAND ${CMAKE_COMMAND}
-DCMAKE_BUILD_TYPE=Debug -GNinja
-DJSON_BuildTests=ON -DJSON_FastTests=ON
-DCMAKE_CXX_FLAGS=-DJSON_BRACE_INIT_COPY_SEMANTICS=1
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_brace_init_copy_semantics
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_brace_init_copy_semantics
COMMAND cd ${PROJECT_BINARY_DIR}/build_brace_init_copy_semantics && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
COMMENT "Compile and test with brace-init copy semantics enabled"
)
###############################################################################
# Enable strict NUL-byte handling.
###############################################################################
@@ -296,25 +311,6 @@ add_custom_target(ci_test_skiplibraryversioncheck
COMMENT "Compile and run a translation unit simulating a mismatched library version, with JSON_SKIP_LIBRARY_VERSION_CHECK defined"
)
###############################################################################
# Disable thread-local storage.
###############################################################################
# Without thread-local storage, the copy constructor cannot bound its descent
# and copies every object and array without the call stack. That path is
# otherwise only reached by values nested deeper than the bound, so this target
# is what runs the whole test suite through it.
add_custom_target(ci_test_no_thread_local
COMMAND ${CMAKE_COMMAND}
-DCMAKE_BUILD_TYPE=Debug -GNinja
-DJSON_BuildTests=ON
-DCMAKE_CXX_FLAGS=-DJSON_NO_THREAD_LOCAL
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_no_thread_local
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_no_thread_local
COMMAND cd ${PROJECT_BINARY_DIR}/build_no_thread_local && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
COMMENT "Compile and test without thread-local storage"
)
###############################################################################
# Coverage.
###############################################################################
@@ -619,7 +615,7 @@ add_custom_target(ci_single_binaries
add_custom_target(ci_benchmarks
COMMAND ${CMAKE_COMMAND}
-DCMAKE_BUILD_TYPE=Release -GNinja
-S${PROJECT_SOURCE_DIR}/benchmarks -B${PROJECT_BINARY_DIR}/build_benchmarks
-S${PROJECT_SOURCE_DIR}/tests/benchmarks -B${PROJECT_BINARY_DIR}/build_benchmarks
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_benchmarks --target json_benchmarks
COMMAND cd ${PROJECT_BINARY_DIR}/build_benchmarks && ./json_benchmarks
COMMENT "Run benchmarks"
+1 -1
View File
@@ -77,7 +77,7 @@ if(CMAKE_CROSSCOMPILING)
endif()
if(NOT DEFINED LIBCPP_VERSION_OUTPUT_CACHED)
try_run(RUN_RESULT_VAR COMPILE_RESULT_VAR
"${CMAKE_BINARY_DIR}" SOURCES "${CMAKE_SOURCE_DIR}/cmake/detect_libcpp_version.cpp"
"${CMAKE_BINARY_DIR}" SOURCES "${CMAKE_CURRENT_LIST_DIR}/detect_libcpp_version.cpp"
RUN_OUTPUT_VARIABLE LIBCPP_VERSION_OUTPUT
COMPILE_OUTPUT_VARIABLE LIBCPP_VERSION_COMPILE_OUTPUT
)
+5 -39
View File
@@ -1,58 +1,24 @@
# generate Bazel BUILD file
#
# usage: cmake -P cmake/scripts/gen_bazel_build_file.cmake (or: make BUILD.bazel)
#
# The header list of the "json" target is derived from the files in include/. Everything else is fixed text below,
# so edit this script rather than BUILD.bazel.
get_filename_component(PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE)
set(PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/../..")
set(BUILD_FILE "${PROJECT_ROOT}/BUILD.bazel")
file(GLOB_RECURSE HEADERS LIST_DIRECTORIES false RELATIVE "${PROJECT_ROOT}" "${PROJECT_ROOT}/include/*.hpp")
list(SORT HEADERS)
set(CONTENT [=[
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_license//rules:license.bzl", "license")
package(
default_applicable_licenses = [":license"],
)
exports_files([
"LICENSE.MIT",
])
license(
name = "license",
license_kinds = ["@rules_license//licenses/spdx:MIT"],
license_text = "LICENSE.MIT",
)
file(GLOB_RECURSE HEADERS LIST_DIRECTORIES false RELATIVE "${PROJECT_ROOT}" "include/*.hpp")
file(WRITE "${BUILD_FILE}" [=[
cc_library(
name = "json",
hdrs = [
]=])
foreach(header ${HEADERS})
string(APPEND CONTENT " \"${header}\",\n")
file(APPEND "${BUILD_FILE}" " \"${header}\",\n")
endforeach()
string(APPEND CONTENT [=[
file(APPEND "${BUILD_FILE}" [=[
],
includes = ["include"],
visibility = ["//visibility:public"],
alwayslink = True,
)
cc_library(
name = "singleheader-json",
hdrs = [
"single_include/nlohmann/json.hpp",
],
includes = ["single_include"],
visibility = ["//visibility:public"],
)
]=])
file(WRITE "${BUILD_FILE}" "${CONTENT}")
-1
View File
@@ -27,7 +27,6 @@ header. See also the [macro overview page](../../features/macros.md).
- [**JSON_HAS_STD_FORMAT**](json_has_std_format.md) - control `std::format`/`std::formatter` support
- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
- [**JSON_NO_THREAD_LOCAL**](json_no_thread_local.md) - switch off the use of `thread_local` storage
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
- [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation
@@ -38,28 +38,6 @@ The default value is `0` (disabled — existing behavior is preserved).
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no effect.
!!! warning "Applies to every single-element list"
The macro does not only affect a single JSON value in braces. **Any** single-element braced list is treated as its
element, so it no longer creates a one-element array:
```cpp
json j1 = {1}; // 1, not [1]
json j2 = {"text"}; // "text", not ["text"]
json j3 = {{1, 2}}; // [1,2], not [[1,2]]
```
Code that relies on these producing arrays must use `json::array()` instead (see below). Lists with more than one
element, and a single `[string, value]` pair such as `{{"key", "value"}}`, which still creates an object, are not
affected. The library's own conversions are not affected either: for example, `std::tuple<int>{5}` still becomes
`[5]`.
!!! note "ABI compatibility"
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_bics`), resulting in
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
!!! tip "Workaround without the macro"
To explicitly create a single-element array without enabling this macro, use `json::array()`:
@@ -1,47 +0,0 @@
# JSON_NO_THREAD_LOCAL
```cpp
#define JSON_NO_THREAD_LOCAL
```
When defined, the library does not use `#!cpp thread_local` storage. This is relevant for the few environments whose
toolchain does not support it.
The copy constructor copies the first levels of a value by copying the containers, which copy their elements, and
completes whatever is nested deeper than that without the call stack, so that copying a value cannot exhaust the stack
however deeply it is nested. It counts the levels it has descended into in a `#!cpp thread_local` variable, as a counter
shared between threads would be raced.
Without that counter, no descent can be bounded safely, so objects and arrays are copied without the call stack right
away. Copying keeps working exactly as it does otherwise - the same values come out, and deeply nested values are copied
just as safely - but copying is slower, because the containers no longer copy themselves. Copying the benchmark
documents takes 9% (`canada.json`) to 34% (`twitter.json`) longer; values built mostly from objects are affected the
most.
## Default definition
By default, `#!cpp JSON_NO_THREAD_LOCAL` is not defined.
```cpp
#undef JSON_NO_THREAD_LOCAL
```
The library defines it by itself for Clang targeting MinGW, which does not survive the `#!cpp thread_local` storage:
copying a value segfaults there, with both old and current Clang versions, while GCC targeting MinGW is unaffected.
## Examples
??? example
The code below forces the library not to use `#!cpp thread_local` storage.
```cpp
#define JSON_NO_THREAD_LOCAL 1
#include <nlohmann/json.hpp>
...
```
## Version history
- Added in version 3.12.1.
@@ -24,14 +24,6 @@ By default, implicit conversions are enabled.
You can prepare existing code by already defining `JSON_USE_IMPLICIT_CONVERSIONS` to `0` and replace any implicit
conversions with calls to [`get`](../basic_json/get.md).
!!! tip "Automatic migration"
The community-maintained clang-tidy check `modernize-nlohmann-json-explicit-conversions` rewrites implicit
conversions into explicit calls to [`get`](../basic_json/get.md); for example, `#!cpp int i = j;` becomes
`#!cpp int i = j.get<int>();`. The check is not part of clang-tidy itself, and it does not catch every case (for
example, constructing a `std::optional` from a JSON value), so review the result. See
[discussion #4610](https://github.com/nlohmann/json/discussions/4610) for how to build and use it.
!!! hint "CMake option"
Implicit conversions can also be controlled with the CMake option
@@ -116,22 +116,18 @@ The library uses the following mapping from JSON values types to BJData types ac
```
Likewise, when a JSON object in the above form is serialized using
[`to_bjdata`](../../api/basic_json/to_bjdata.md), it is automatically converted into a compact BJData ND-array.
[`to_bjdata`](../../api/basic_json/to_bjdata.md), it is automatically converted into a compact BJData ND-array. When
the 1-dimensional vector stored in `"_ArraySize_"` contains a single integer or two integers with one being 1, a
regular 1-D optimized array is generated instead.
When parsing, an ND-array whose dimension vector is empty, contains a single integer, contains two integers with the
first being 1, or contains a 0 is returned as a regular (possibly empty) array rather than an annotated object.
An object is only converted if the annotation describes a packed array that is parsed back into the same annotated
object; otherwise it is serialized as a regular JSON object, so the annotation is never lost in a round trip. This requires
all of the following:
An object is only converted if the annotation actually describes a packed array; otherwise it is serialized as a
regular JSON object. This requires all of the following:
- `"_ArrayType_"` is one of `uint8`, `int8`, `uint16`, `int16`, `uint32`, `int32`, `uint64`, `int64`, `single`,
`double`, `char`, or `byte`,
- `"_ArraySize_"` is an array, since the dimensions are written as the ND-array header's length,
- `"_ArraySize_"` has at least two entries and is not a 1×N row vector (first entry 1), since other shapes are
parsed back as a regular array,
- every entry of `"_ArraySize_"` is a positive integer, and their product is representable as a `std::size_t`,
- `"_ArrayData_"` is an array holding exactly that many elements, and
- every entry of `"_ArraySize_"` is a non-negative integer, and their product is representable as a `std::size_t`,
- `"_ArrayData_"` holds exactly that many elements, and
- every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"` (a floating-point number for
`single` and `double`, an integer otherwise).
-7
View File
@@ -91,13 +91,6 @@ security reasons (e.g., Intel Software Guard Extensions (SGX)).
See [full documentation of `JSON_NO_IO`](../api/macros/json_no_io.md).
## `JSON_NO_THREAD_LOCAL`
When defined, the library does not use `#!cpp thread_local` storage. Copying a value then always avoids the call stack
rather than descending into a bounded number of levels first, which is slower but yields the same values.
See [full documentation of `JSON_NO_THREAD_LOCAL`](../api/macros/json_no_thread_local.md).
## `JSON_SKIP_LIBRARY_VERSION_CHECK`
When defined, the library will not create a compiler warning when a different version of the library was already
-3
View File
@@ -15,9 +15,6 @@ The complete default namespace name is derived as follows:
- [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) defined non-zero appends `_diag`.
- [`JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md)
defined non-zero appends `_ldvcmp`.
- [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) defined non-zero appends `_dp`.
- [`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md) defined non-zero appends
`_bics`.
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by
underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component)
below.
@@ -176,12 +176,6 @@ You can prepare existing code by already defining
conversions with calls to [`get`](../api/basic_json/get.md), [`get_to`](../api/basic_json/get_to.md),
[`get_ref`](../api/basic_json/get_ref.md), or [`get_ptr`](../api/basic_json/get_ptr.md).
!!! tip "Automatic migration"
The community-maintained clang-tidy check `modernize-nlohmann-json-explicit-conversions` rewrites most implicit
conversions into calls to [`get`](../api/basic_json/get.md). It is not part of clang-tidy itself; see
[discussion #4610](https://github.com/nlohmann/json/discussions/4610) for how to build and use it.
=== "Deprecated"
```cpp
-1
View File
@@ -292,7 +292,6 @@ nav:
- 'JSON_HAS_THREE_WAY_COMPARISON': api/macros/json_has_three_way_comparison.md
- 'JSON_NOEXCEPTION': api/macros/json_noexception.md
- 'JSON_NO_IO': api/macros/json_no_io.md
- 'JSON_NO_THREAD_LOCAL': api/macros/json_no_thread_local.md
- 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md
- 'JSON_SKIP_UNSUPPORTED_COMPILER_CHECK': api/macros/json_skip_unsupported_compiler_check.md
- 'JSON_STRICT_NUL_HANDLING': api/macros/json_strict_nul_handling.md
+4 -15
View File
@@ -34,10 +34,6 @@
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
#endif
#ifndef JSON_BRACE_INIT_COPY_SEMANTICS
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
@@ -56,27 +52,20 @@
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
#endif
#if JSON_BRACE_INIT_COPY_SEMANTICS
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS _bics
#else
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d) json_abi ## a ## b ## c ## d
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d)
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS)
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
@@ -471,30 +471,6 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
j = { std::get<Idx>(t)... };
}
#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*/)
{
BasicJsonType element(std::get<0>(t));
// same test as the initializer-list constructor, including the cast that
// keeps a string type constructible from 0 from selecting operator[](key)
const bool is_member = element.is_array() && element.size() == 2
&& element[static_cast<typename BasicJsonType::size_type>(0)].is_string();
if (is_member)
{
j = BasicJsonType::object({std::move(element)});
}
else
{
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*/)
{
+3 -99
View File
@@ -11,10 +11,8 @@
#include <cstdint> // uint8_t
#include <cstddef> // size_t
#include <functional> // hash
#include <vector> // vector
#include <nlohmann/detail/abi_macros.hpp>
#include <nlohmann/detail/recursion_depth_limit.hpp>
#include <nlohmann/detail/value_t.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
@@ -28,9 +26,6 @@ inline std::size_t combine(std::size_t seed, std::size_t h) noexcept
return seed;
}
template<typename BasicJsonType>
std::size_t hash_iteratively(const BasicJsonType& j);
/*!
@brief hash a JSON value
@@ -38,21 +33,12 @@ The hash function tries to rely on std::hash where possible. Furthermore, the
type of the JSON value is taken into account to have different hash values for
null, 0, 0U, and false, etc.
Hashing an array or an object hashes its elements, which used to call this
function again once per nesting level, so a value nested deeply enough
exhausted the call stack and terminated the process. The descent is bounded
here: once @ref recursion_depth_limit levels have been entered, @ref
hash_iteratively hashes what is left without the call stack. A value nested
less deeply than that - all but a vanishing minority - is hashed exactly as
before, without allocating.
@tparam BasicJsonType basic_json specialization
@param j JSON value to hash
@param depth nesting level of @a j, counted from the value passed by the caller
@return hash value of j
*/
template<typename BasicJsonType>
std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
std::size_t hash(const BasicJsonType& j)
{
using string_t = typename BasicJsonType::string_t;
using number_integer_t = typename BasicJsonType::number_integer_t;
@@ -70,32 +56,22 @@ std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
case BasicJsonType::value_t::object:
{
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
{
return hash_iteratively(j);
}
auto seed = combine(type, j.size());
for (const auto& element : j.items())
{
const auto h = std::hash<string_t> {}(element.key());
seed = combine(seed, h);
seed = combine(seed, hash(element.value(), depth + 1));
seed = combine(seed, hash(element.value()));
}
return seed;
}
case BasicJsonType::value_t::array:
{
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
{
return hash_iteratively(j);
}
auto seed = combine(type, j.size());
for (const auto& element : j)
{
seed = combine(seed, hash(element, depth + 1));
seed = combine(seed, hash(element));
}
return seed;
}
@@ -151,77 +127,5 @@ std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
}
}
/// an array or object whose elements @ref hash_iteratively is hashing
template<typename BasicJsonType>
struct hash_frame
{
hash_frame(const BasicJsonType* value_, std::size_t seed_) noexcept
: value(value_), position(value_->cbegin()), seed(seed_)
{}
const BasicJsonType* value;
typename BasicJsonType::const_iterator position;
std::size_t seed;
};
/*!
@brief hash the array or object @a j without the call stack
Computes the same value as @ref hash, keeping the arrays and objects it has
entered on an explicit stack instead of descending into them. Only reached for
values nested deeper than @ref recursion_depth_limit.
@tparam BasicJsonType basic_json specialization
@param j array or object to hash
@return hash value of j
*/
template<typename BasicJsonType>
std::size_t hash_iteratively(const BasicJsonType& j)
{
using string_t = typename BasicJsonType::string_t;
std::vector<hash_frame<BasicJsonType>> stack;
stack.emplace_back(&j, combine(static_cast<std::size_t>(j.type()), j.size()));
while (true)
{
// a copy, as entering an element below can reallocate the stack; the
// frame itself is only changed through stack.back()
const hash_frame<BasicJsonType> frame = stack.back();
if (frame.position == frame.value->cend())
{
// all elements are hashed: fold this value's hash into its parent's
// seed, exactly where the recursive version returns it
const std::size_t h = frame.seed;
stack.pop_back();
if (stack.empty())
{
return h;
}
stack.back().seed = combine(stack.back().seed, h);
continue;
}
if (frame.value->is_object())
{
stack.back().seed = combine(stack.back().seed, std::hash<string_t> {}(frame.position.key()));
}
// advance before entering the element, which pushes onto the stack
const BasicJsonType& element = *frame.position;
++stack.back().position;
if (element.is_structured())
{
stack.emplace_back(&element, combine(static_cast<std::size_t>(element.type()), element.size()));
}
else
{
stack.back().seed = combine(stack.back().seed, hash(element));
}
}
}
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
+4 -9
View File
@@ -186,15 +186,6 @@
#define JSON_NO_UNIQUE_ADDRESS
#endif
// Clang targeting MinGW does not survive the thread_local storage the copy
// constructor uses to bound its descent: every test that copies a value
// segfaults with clang 11.0.1 and clang 18.1.8, while the same tests pass with
// GCC targeting MinGW and with every other toolchain the library is tested on.
// Copying works the same way without the counter, only more slowly.
#if !defined(JSON_NO_THREAD_LOCAL) && defined(__clang__) && defined(__MINGW32__)
#define JSON_NO_THREAD_LOCAL 1
#endif
// disable documentation warnings on clang
#if defined(__clang__)
#pragma clang diagnostic push
@@ -813,6 +804,10 @@ void templated_json_throw(ExceptionType exception)
#define JSON_USE_GLOBAL_UDLS 1
#endif
#ifndef JSON_BRACE_INIT_COPY_SEMANTICS
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
#endif
#ifndef JSON_STRICT_NUL_HANDLING
#define JSON_STRICT_NUL_HANDLING 0
#endif
+1 -1
View File
@@ -26,6 +26,7 @@
#undef JSON_NO_UNIQUE_ADDRESS
#undef JSON_DISABLE_ENUM_SERIALIZATION
#undef JSON_USE_GLOBAL_UDLS
#undef JSON_BRACE_INIT_COPY_SEMANTICS
#undef JSON_STRICT_NUL_HANDLING
#ifndef JSON_TEST_KEEP_MACROS
@@ -44,7 +45,6 @@
#undef JSON_HAS_STD_FORMAT
#undef JSON_HAS_STATIC_RTTI
#undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#undef JSON_BRACE_INIT_COPY_SEMANTICS
#endif
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
@@ -881,6 +881,8 @@ class binary_writer
return ubjson_prefix(v, use_bjdata) == first_prefix;
});
std::vector<CharType> bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type
// an optimized array of a valueless type carries no payload, so a
// reader has nothing but the declared count to bound the allocation
// by and refuses an excessive one. Write the unoptimized form for
@@ -891,7 +893,7 @@ class binary_writer
&& j.m_data.m_value.array->size() > detail::max_valueless_container_size;
if (same_prefix && !excessive_valueless
&& !(use_bjdata && is_bjdata_excluded_type_marker(first_prefix)))
&& !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end()))
{
prefix_required = false;
oa.write_character(to_char_type('$'));
@@ -995,7 +997,9 @@ class binary_writer
return ubjson_prefix(v, use_bjdata) == first_prefix;
});
if (same_prefix && !(use_bjdata && is_bjdata_excluded_type_marker(first_prefix)))
std::vector<CharType> bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type
if (same_prefix && !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end()))
{
prefix_required = false;
oa.write_character(to_char_type('$'));
@@ -1722,21 +1726,6 @@ class binary_writer
}
}
/*!
@brief whether BJData forbids @a marker as the type of an optimized array
or object
Containers, strings, high-precision numbers, booleans and null cannot be
declared as the single type of an optimized container in BJData; such a
container is written unoptimized. The reader rejects them with the same
list (binary_reader::bjd_optimized_type_markers).
*/
static constexpr bool is_bjdata_excluded_type_marker(const CharType marker) noexcept
{
return marker == '[' || marker == '{' || marker == 'S' || marker == 'H'
|| marker == 'T' || marker == 'F' || marker == 'N' || marker == 'Z';
}
static constexpr CharType get_ubjson_float_prefix(float /*unused*/)
{
return 'd'; // float 32
@@ -1811,19 +1800,8 @@ class binary_writer
return true;
}
// the reader only restores an annotated object from an ND-array header
// with at least two dimensions: an empty dimension vector, a single
// dimension, or a 1xN row vector is read back as a plain array, which
// would silently drop the annotation, so such an object falls back to
// a plain object encoding instead
const auto& dims = value.at(key);
if (dims.size() < 2 || (dims.size() == 2 && dims.at(0).is_number_integer() && dims.at(0).template get<std::int64_t>() == 1))
{
return true;
}
std::size_t len = 1;
for (const auto& el : dims)
std::size_t len = (value.at(key).empty() ? 0 : 1);
for (const auto& el : value.at(key))
{
// a dimension is read as an unsigned value below, so anything that
// is not a non-negative integer is rejected: a non-integer entry
@@ -1845,26 +1823,15 @@ class binary_writer
return true;
}
const auto dim_size = static_cast<std::size_t>(dim);
// the reader turns an ND-array with any zero dimension into an
// empty plain array, dropping the annotation, so keep the object
if (dim_size == 0)
{
return true;
}
if (len > (std::numeric_limits<std::size_t>::max)() / dim_size)
if (dim_size != 0 && len > (std::numeric_limits<std::size_t>::max)() / dim_size)
{
return true;
}
len *= dim_size;
}
// the elements are written from _ArrayData_ as a flat list, so it has
// to be an array: size() is 0 for null and 1 for any other scalar, and
// iterating an object visits its values, so any of these could match
// the dimensions by accident and be encoded as an unrelated ND-array
key = "_ArrayData_";
if (!value.at(key).is_array() || value.at(key).size() != len)
if (value.at(key).size() != len)
{
return true;
}
+11 -5
View File
@@ -30,7 +30,6 @@
#include <nlohmann/detail/meta/cpp_future.hpp>
#include <nlohmann/detail/output/binary_writer.hpp>
#include <nlohmann/detail/output/output_adapters.hpp>
#include <nlohmann/detail/recursion_depth_limit.hpp>
#include <nlohmann/detail/string_concat.hpp>
#include <nlohmann/detail/value_t.hpp>
@@ -134,7 +133,7 @@ class serializer
Serializing a container descends into its elements, so a value nested deeply
enough used to exhaust the call stack and terminate the process with no
exception to catch. The descent is bounded here: once @ref recursion_depth_limit
exception to catch. The descent is bounded here: once @ref dump_depth_limit
levels have been entered, @ref dump_iteratively writes out what is left
without the call stack. A value nested less deeply than that - all but a
vanishing minority - is written by exactly the code that always wrote it.
@@ -149,7 +148,7 @@ class serializer
{
case value_t::object:
{
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
if (JSON_HEDLEY_UNLIKELY(depth >= dump_depth_limit()))
{
dump_iteratively(val, current_indent);
return;
@@ -224,7 +223,7 @@ class serializer
case value_t::array:
{
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
if (JSON_HEDLEY_UNLIKELY(depth >= dump_depth_limit()))
{
dump_iteratively(val, current_indent);
return;
@@ -409,12 +408,19 @@ class serializer
}
private:
/// the number of levels @ref dump_internal descends into before it hands
/// over to @ref dump_iteratively
static constexpr std::size_t dump_depth_limit()
{
return 128;
}
/*!
@brief write out @a val and everything below it without the call stack
Emits the same bytes as @ref dump_internal, keeping the containers it has
entered on an explicit stack instead of descending into them. Only reached
for values nested deeper than @ref recursion_depth_limit, which is why it is not
for values nested deeper than @ref dump_depth_limit, which is why it is not
written for speed: walking every value this way measured up to 20% slower on
object-heavy documents than letting the compiler drive the descent.
*/
@@ -1,35 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
#include <cstddef> // size_t
#include <nlohmann/detail/abi_macros.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
/*!
@brief the number of nesting levels an operation recurses into
Operations that walk a value (serializing, hashing, merging, ...) recurse once
per nesting level, which is fastest, but a value nested deeply enough would
exhaust the call stack. So they recurse only this many levels deep and finish
whatever lies below with an explicit stack. All of them share this limit.
@sa https://github.com/nlohmann/json/issues/5387
*/
constexpr std::size_t recursion_depth_limit() noexcept
{
return 128;
}
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
+58 -532
View File
@@ -28,14 +28,14 @@
#pragma GCC diagnostic ignored "-Wignored-attributes"
#endif
#include <algorithm> // all_of, find, for_each, none_of
#include <algorithm> // all_of, find, for_each
#include <cstddef> // nullptr_t, ptrdiff_t, size_t
#include <functional> // hash, less
#include <initializer_list> // initializer_list
#ifndef JSON_NO_IO
#include <iosfwd> // istream, ostream
#endif // JSON_NO_IO
#include <iterator> // make_move_iterator, random_access_iterator_tag
#include <iterator> // random_access_iterator_tag
#include <memory> // unique_ptr
#include <string> // string, stoi, to_string
#include <utility> // declval, forward, move, pair, swap
@@ -68,7 +68,6 @@
#include <nlohmann/detail/output/binary_writer.hpp>
#include <nlohmann/detail/output/output_adapters.hpp>
#include <nlohmann/detail/output/serializer.hpp>
#include <nlohmann/detail/recursion_depth_limit.hpp>
#include <nlohmann/detail/value_t.hpp>
#include <nlohmann/json_fwd.hpp>
#include <nlohmann/ordered_map.hpp>
@@ -897,352 +896,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return j;
}
#ifndef JSON_NO_THREAD_LOCAL
/// the number of levels an operation descends into before it finishes the
/// value below it without the call stack
static constexpr std::uint8_t nesting_depth_limit()
{
return 128;
}
/*!
@brief how many levels the operation going on in this thread has descended into
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.
*/
static std::uint8_t& nesting_depth() noexcept
{
static thread_local std::uint8_t depth = 0; // NOLINT(misc-use-internal-linkage)
return depth;
}
#endif
/*!
@brief counts one level of a bounded descent for as long as it runs, and
reports whether the descent was still within the limit when it began
Looks the count up and tests it against the limit itself, rather than
leaving that to the caller: either way it is reached exactly once, so
there is nothing to be gained by making the caller do it.
Does nothing and is never @ref okay without thread-local storage, where no
descent can be bounded at all: a caller that only descends while this says
it may always ends up finishing without the call stack, exactly as if every
value were nested past the limit.
*/
class nesting_depth_guard
{
public:
nesting_depth_guard() noexcept
#ifdef JSON_NO_THREAD_LOCAL
: m_okay(false)
#else
: m_okay(nesting_depth() < nesting_depth_limit())
#endif
{
#ifndef JSON_NO_THREAD_LOCAL
++nesting_depth();
#endif
}
~nesting_depth_guard()
{
#ifndef JSON_NO_THREAD_LOCAL
--nesting_depth();
#endif
}
nesting_depth_guard(const nesting_depth_guard&) = delete;
nesting_depth_guard& operator=(const nesting_depth_guard&) = delete;
nesting_depth_guard(nesting_depth_guard&&) = delete;
nesting_depth_guard& operator=(nesting_depth_guard&&) = delete;
bool okay() const noexcept
{
return m_okay;
}
private:
bool m_okay;
};
/// an entry of the iterative deep copy's worklist: a structured value and
/// the value that is to become its copy
using copy_worklist_t = std::vector<std::pair<const basic_json*, basic_json*>>;
/// scratch space to build the key skeleton of an object copy in one go
using copy_scratch_t = std::vector<std::pair<typename object_t::key_type, basic_json>>;
/// @brief copy everything of @a src into @a dst but its type and value
static void copy_metadata(const basic_json& src, basic_json& dst)
{
// a custom base class is only required to be copy-constructible and
// move-assignable, so the copy has to go through a temporary
static_cast<json_base_class_t&>(dst) = json_base_class_t(static_cast<const json_base_class_t&>(src));
#if JSON_DIAGNOSTIC_POSITIONS
dst.start_position = src.start_position;
dst.end_position = src.end_position;
#endif
}
/*!
@brief copy the value of @a src into @a dst, which must not be structured
Objects and arrays are left alone: creating those is the one thing the copy
constructor and @ref copy_shallow do differently from one another, and it is
the reason copying a value can descend at all.
*/
/// @note inlined on purpose: both callers have already told an object or an
/// array apart from the rest, and letting the compiler fold that test
/// into this switch is worth a few percent when copying a value made
/// mostly of numbers
JSON_HEDLEY_ALWAYS_INLINE
static void copy_leaf_value(const basic_json& src, basic_json& dst)
{
switch (src.m_data.m_type)
{
case value_t::string:
{
dst.m_data.m_value = *src.m_data.m_value.string;
break;
}
case value_t::binary:
{
dst.m_data.m_value = *src.m_data.m_value.binary;
break;
}
case value_t::boolean:
{
dst.m_data.m_value = src.m_data.m_value.boolean;
break;
}
case value_t::number_integer:
{
dst.m_data.m_value = src.m_data.m_value.number_integer;
break;
}
case value_t::number_unsigned:
{
dst.m_data.m_value = src.m_data.m_value.number_unsigned;
break;
}
case value_t::number_float:
{
dst.m_data.m_value = src.m_data.m_value.number_float;
break;
}
case value_t::object:
case value_t::array:
case value_t::null:
case value_t::discarded:
default:
break;
}
}
/*!
@brief copy everything of @a src into the null value @a dst but the children
Objects and arrays are not copied here; they are appended to @a worklist to
be created later by @ref copy_iteratively. Until that happens, @a dst remains
a null value, so that a partially built copy can be destroyed at any point
without ever violating the class invariants.
*/
static void copy_shallow(const basic_json& src, basic_json& dst, copy_worklist_t& worklist)
{
copy_metadata(src, dst);
if (src.m_data.m_type == value_t::object || src.m_data.m_type == value_t::array)
{
// defer: dst stays a null value until its container exists
worklist.emplace_back(&src, &dst);
return;
}
copy_leaf_value(src, dst);
// only now that the value exists may the type be set: had the creation
// of the value thrown, dst would have been left as a valid null value
dst.m_data.m_type = src.m_data.m_type;
}
/// @brief create the copy of the array @a src in @a dst
/// @note structured elements are appended to @a worklist instead
static void copy_array_level(const basic_json& src, basic_json& dst, copy_worklist_t& worklist)
{
const array_t& src_array = *src.m_data.m_value.array;
// create all elements up front: growing the array afterwards could
// invalidate the pointers that are handed to the worklist; resize()
// rather than the fill constructor, because not every array type
// provides the latter (e.g., ones without a matching allocator-aware
// fill constructor)
dst.m_data.m_value.array = create<array_t>();
dst.m_data.m_value.array->resize(src_array.size());
auto dst_it = dst.m_data.m_value.array->begin();
for (auto src_it = src_array.cbegin(); src_it != src_array.cend(); ++src_it, ++dst_it)
{
copy_shallow(*src_it, *dst_it, worklist);
}
}
/// @brief create the copy of the object @a src in @a dst
/// @note structured values are appended to @a worklist instead
static void copy_object_level(const basic_json& src, basic_json& dst,
copy_worklist_t& worklist, copy_scratch_t& scratch)
{
const object_t& src_object = *src.m_data.m_value.object;
// build the complete key skeleton and hand it to the object's range
// constructor: adding the keys one by one would be quadratic for object
// types that are backed by a vector, such as nlohmann::ordered_map
scratch.clear();
scratch.reserve(src_object.size());
for (const auto& element : src_object)
{
scratch.emplace_back(element.first, basic_json());
}
dst.m_data.m_value.object = create<object_t>(std::make_move_iterator(scratch.begin()),
std::make_move_iterator(scratch.end()));
scratch.clear();
// pair every value of the copy with its counterpart in the original;
// both are enumerated in the same order for every object type with a
// deterministic order, so the lookup is only needed for exotic ones
auto src_it = src_object.cbegin();
for (auto& element : *dst.m_data.m_value.object)
{
if (JSON_HEDLEY_LIKELY(src_it != src_object.cend() && src_it->first == element.first))
{
copy_shallow(src_it->second, element.second, worklist);
++src_it;
}
else
{
const auto found = src_object.find(element.first);
JSON_ASSERT(found != src_object.cend());
copy_shallow(found->second, element.second, worklist);
}
}
}
/*!
@brief deep-copy the object or array @a src into this value without recursing
The values whose copy has not been created yet are kept on an explicit
worklist rather than on the call stack. This is only reached for values
nested deeper than @ref nesting_depth_limit levels, which is why it copies
every container by hand instead of letting the container do it: the fast
ways of doing so would descend into the elements and defeat the purpose.
*/
void copy_iteratively(const basic_json& src)
{
copy_worklist_t worklist;
copy_scratch_t scratch;
const basic_json* src_value = &src;
basic_json* dst_value = this;
for (;;)
{
if (src_value->m_data.m_type == value_t::array)
{
copy_array_level(*src_value, *dst_value, worklist);
}
else
{
copy_object_level(*src_value, *dst_value, worklist, scratch);
}
// the container is complete and will not be modified again
dst_value->set_parents();
if (worklist.empty())
{
break;
}
const auto& next = worklist.back();
src_value = next.first;
dst_value = next.second;
worklist.pop_back();
// the value stops being a null value exactly here
dst_value->m_data.m_type = src_value->m_data.m_type;
}
}
/*!
@brief copy one level of the object or array @a src into this value
The container copies its own elements, which is the fastest way to fill it.
Every element that is structured itself comes back to @ref copy_structured.
*/
void copy_level(const basic_json& src)
{
if (m_data.m_type == value_t::object)
{
m_data.m_value = *src.m_data.m_value.object;
}
else
{
m_data.m_value = *src.m_data.m_value.array;
}
set_parents();
}
/*!
@brief deep-copy the object or array @a src into this value
Copying a container copies its elements, so a value nested deeply enough
used to exhaust the call stack. The descent is bounded here: the first
@ref nesting_depth_limit levels are copied by the containers themselves, just
as they always were, and anything below that is copied without the call
stack by @ref copy_iteratively. Copying a value can therefore no longer
exhaust the stack, however deeply it is nested, just like destroying one
cannot since #1436.
Nothing has to be scanned or built by hand to reach that: a value that is
not nested deeper than the limit - all but a vanishing minority - is copied
exactly as it was before, and this whole detour costs it one counter.
@sa https://github.com/nlohmann/json/issues/5387
*/
void copy_structured(const basic_json& src)
{
const nesting_depth_guard guard;
if (JSON_HEDLEY_LIKELY(guard.okay()))
{
copy_level(src);
return;
}
// Finish this value without descending any further. It is completed
// before this returns, so a copy made by a custom base class - or by
// anything else that runs while a copy is going on - is unaffected by
// the copy it is nested in.
copy_iteratively(src);
}
public:
//////////////////////////
// JSON parser callback //
@@ -1622,15 +1275,60 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// check of passed value is valid
other.assert_invariant();
if (m_data.m_type == value_t::object || m_data.m_type == value_t::array)
switch (m_data.m_type)
{
// copying the container directly would call this constructor again
// for every element, once per nesting level
copy_structured(other);
}
else
{
copy_leaf_value(other, *this);
case value_t::object:
{
m_data.m_value = *other.m_data.m_value.object;
break;
}
case value_t::array:
{
m_data.m_value = *other.m_data.m_value.array;
break;
}
case value_t::string:
{
m_data.m_value = *other.m_data.m_value.string;
break;
}
case value_t::boolean:
{
m_data.m_value = other.m_data.m_value.boolean;
break;
}
case value_t::number_integer:
{
m_data.m_value = other.m_data.m_value.number_integer;
break;
}
case value_t::number_unsigned:
{
m_data.m_value = other.m_data.m_value.number_unsigned;
break;
}
case value_t::number_float:
{
m_data.m_value = other.m_data.m_value.number_float;
break;
}
case value_t::binary:
{
m_data.m_value = *other.m_data.m_value.binary;
break;
}
case value_t::null:
case value_t::discarded:
default:
break;
}
set_parents();
@@ -3913,54 +3611,17 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", first.m_object->type_name()), first.m_object));
}
update_members(first, last, merge_objects, 0);
}
private:
/// @brief an object @ref update_members_iteratively or @ref
/// merge_patch_iteratively is merging into, and the members still to merge
struct merge_frame
{
merge_frame(basic_json* target_, const_iterator position_, const_iterator last_) noexcept
: target(target_), position(std::move(position_)), last(std::move(last_))
{}
basic_json* target;
const_iterator position;
const_iterator last;
};
/*!
@brief the members loop of @ref update, for this object and range
Merging a nested object calls this function again, once per nesting
level, so a value nested deeply enough used to exhaust the call stack and
terminate the process. The descent is bounded here: once @ref
detail::recursion_depth_limit levels have been entered, @ref
update_members_iteratively merges what is left without the call stack.
@param[in] depth nesting level of this object, counted from the object
@ref update was called on
*/
void update_members(const const_iterator& first, const const_iterator& last, const bool merge_objects, const std::size_t depth)
{
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
{
update_members_iteratively(first, last);
return;
}
for (auto it = first; it != last; ++it)
{
if (merge_objects && it.value().is_object())
{
const auto it2 = m_data.m_value.object->find(it.key());
auto it2 = m_data.m_value.object->find(it.key());
// Only recurse when the existing value is itself an object.
// Otherwise overwrite, matching the documented "all other values
// are overwritten as usual" behavior (see #5402).
if (it2 != m_data.m_value.object->end() && it2->second.is_object())
{
it2->second.update_members(it.value().cbegin(), it.value().cend(), true, depth + 1);
it2->second.update(it.value(), true);
#if JSON_DIAGNOSTICS
it2->second.set_parents();
#endif
@@ -3974,64 +3635,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
}
/*!
@brief merge @a first to @a last into this object without the call stack
Does the same as @ref update_members with `merge_objects` set, keeping the
objects whose merge was interrupted by a nested one on an explicit stack
instead of descending into them. A nested object is still merged
completely before the next member, in the same order as the recursive
version. Only reached for values nested deeper than @ref
detail::recursion_depth_limit.
*/
void update_members_iteratively(const_iterator first, const_iterator last)
{
std::vector<merge_frame> stack;
basic_json* target = this;
while (true)
{
if (first == last)
{
if (stack.empty())
{
break;
}
// a nested object is merged: continue with its parent
#if JSON_DIAGNOSTICS
target->set_parents();
#endif
target = stack.back().target;
first = stack.back().position;
last = stack.back().last;
stack.pop_back();
continue;
}
if (first.value().is_object())
{
const auto it2 = target->m_data.m_value.object->find(first.key());
if (it2 != target->m_data.m_value.object->end() && it2->second.is_object())
{
const basic_json& source = first.value();
++first;
stack.emplace_back(target, first, last);
target = &it2->second;
first = source.cbegin();
last = source.cend();
continue;
}
}
target->m_data.m_value.object->operator[](first.key()) = first.value();
#if JSON_DIAGNOSTICS
target->m_data.m_value.object->operator[](first.key()).m_parent = target;
#endif
++first;
}
}
public:
/// @brief exchanges the values
/// @sa https://json.nlohmann.me/api/basic_json/swap/
void swap(reference other) noexcept (
@@ -5926,30 +5529,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief applies a JSON Merge Patch
/// @sa https://json.nlohmann.me/api/basic_json/merge_patch/
void merge_patch(const basic_json& apply_patch)
{
apply_merge_patch(apply_patch, 0);
}
private:
/*!
@brief @ref merge_patch, for a patch at nesting level @a depth
Applying a nested object calls this function again, once per nesting
level, so a patch nested deeply enough used to exhaust the call stack and
terminate the process. The descent is bounded here: once @ref
detail::recursion_depth_limit levels have been entered, @ref
merge_patch_iteratively applies what is left without the call stack.
*/
void apply_merge_patch(const basic_json& apply_patch, const std::size_t depth)
{
if (apply_patch.is_object())
{
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
{
merge_patch_iteratively(apply_patch);
return;
}
if (!is_object())
{
*this = object();
@@ -5962,7 +5544,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
else
{
operator[](it.key()).apply_merge_patch(it.value(), depth + 1);
operator[](it.key()).merge_patch(it.value());
}
}
}
@@ -5972,62 +5554,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
}
/*!
@brief apply @a apply_patch to this value without the call stack
Does the same as @ref merge_patch, keeping the objects being patched on an
explicit stack instead of descending into them. A nested object is still
patched completely before the next member, in the same order as the
recursive version. Only reached for patches nested deeper than @ref
detail::recursion_depth_limit.
*/
void merge_patch_iteratively(const basic_json& apply_patch)
{
std::vector<merge_frame> stack;
// patch `target` with `patch`, or start patching it member by member
const auto apply = [&stack](basic_json & target, const basic_json & patch)
{
if (patch.is_object())
{
if (!target.is_object())
{
target = basic_json::object();
}
stack.emplace_back(&target, patch.cbegin(), patch.cend());
}
else
{
target = patch;
}
};
apply(*this, apply_patch);
while (!stack.empty())
{
// a copy, as applying a member below can reallocate the stack;
// the frame itself is only changed through stack.back()
const merge_frame frame = stack.back();
if (frame.position == frame.last)
{
stack.pop_back();
continue;
}
const const_iterator member = frame.position;
++stack.back().position;
if (member.value().is_null())
{
frame.target->erase(member.key());
}
else
{
apply(frame.target->operator[](member.key()), member.value());
}
}
}
public:
/// @}
};
-720
View File
@@ -215,126 +215,6 @@
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_dp -->
<Type Name="nlohmann::json_abi_dp::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_dp::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_dp_v3_12_0 -->
<Type Name="nlohmann::json_abi_dp_v3_12_0::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp_v3_12_0::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_dp_v3_12_0::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_bics -->
<Type Name="nlohmann::json_abi_bics::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_bics::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_bics::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_bics_v3_12_0 -->
<Type Name="nlohmann::json_abi_bics_v3_12_0::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_bics_v3_12_0::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_bics_v3_12_0::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_diag_ldvcmp -->
<Type Name="nlohmann::json_abi_diag_ldvcmp::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp::detail::value_t::null">null</DisplayString>
@@ -395,604 +275,4 @@
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_diag_dp -->
<Type Name="nlohmann::json_abi_diag_dp::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_diag_dp::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_diag_dp_v3_12_0 -->
<Type Name="nlohmann::json_abi_diag_dp_v3_12_0::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp_v3_12_0::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_diag_dp_v3_12_0::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_diag_bics -->
<Type Name="nlohmann::json_abi_diag_bics::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_bics::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_diag_bics::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_diag_bics_v3_12_0 -->
<Type Name="nlohmann::json_abi_diag_bics_v3_12_0::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_bics_v3_12_0::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_diag_bics_v3_12_0::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_ldvcmp_dp -->
<Type Name="nlohmann::json_abi_ldvcmp_dp::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_ldvcmp_dp::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_ldvcmp_dp_v3_12_0 -->
<Type Name="nlohmann::json_abi_ldvcmp_dp_v3_12_0::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_v3_12_0::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_ldvcmp_dp_v3_12_0::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_ldvcmp_bics -->
<Type Name="nlohmann::json_abi_ldvcmp_bics::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_ldvcmp_bics::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_ldvcmp_bics_v3_12_0 -->
<Type Name="nlohmann::json_abi_ldvcmp_bics_v3_12_0::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_bics_v3_12_0::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_ldvcmp_bics_v3_12_0::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_dp_bics -->
<Type Name="nlohmann::json_abi_dp_bics::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp_bics::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_dp_bics::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_dp_bics_v3_12_0 -->
<Type Name="nlohmann::json_abi_dp_bics_v3_12_0::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_dp_bics_v3_12_0::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_dp_bics_v3_12_0::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_diag_ldvcmp_dp -->
<Type Name="nlohmann::json_abi_diag_ldvcmp_dp::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_diag_ldvcmp_dp::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0 -->
<Type Name="nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_diag_ldvcmp_dp_v3_12_0::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_diag_ldvcmp_bics -->
<Type Name="nlohmann::json_abi_diag_ldvcmp_bics::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_diag_ldvcmp_bics::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0 -->
<Type Name="nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_diag_ldvcmp_bics_v3_12_0::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_diag_dp_bics -->
<Type Name="nlohmann::json_abi_diag_dp_bics::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_diag_dp_bics::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_diag_dp_bics_v3_12_0 -->
<Type Name="nlohmann::json_abi_diag_dp_bics_v3_12_0::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_dp_bics_v3_12_0::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_diag_dp_bics_v3_12_0::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_ldvcmp_dp_bics -->
<Type Name="nlohmann::json_abi_ldvcmp_dp_bics::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_ldvcmp_dp_bics::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0 -->
<Type Name="nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_ldvcmp_dp_bics_v3_12_0::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_diag_ldvcmp_dp_bics -->
<Type Name="nlohmann::json_abi_diag_ldvcmp_dp_bics::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_diag_ldvcmp_dp_bics::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
<!-- Namespace nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0 -->
<Type Name="nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::basic_json&lt;*&gt;">
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::null">null</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::object">{*(m_data.m_value.object)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::array">{*(m_data.m_value.array)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::string">{*(m_data.m_value.string)}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::boolean">{m_data.m_value.boolean}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::number_integer">{m_data.m_value.number_integer}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::number_unsigned">{m_data.m_value.number_unsigned}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::number_float">{m_data.m_value.number_float}</DisplayString>
<DisplayString Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::discarded">discarded</DisplayString>
<Expand>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::object">
*(m_data.m_value.object),view(simple)
</ExpandedItem>
<ExpandedItem Condition="m_data.m_type == nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::detail::value_t::array">
*(m_data.m_value.array),view(simple)
</ExpandedItem>
</Expand>
</Type>
<!-- Skip the pair first/second members in the treeview while traversing a map.
Only works in VS 2015 Update 2 and beyond using the new visualization -->
<Type Name="std::pair&lt;*, nlohmann::json_abi_diag_ldvcmp_dp_bics_v3_12_0::basic_json&lt;*&gt;&gt;" IncludeView="MapHelper">
<DisplayString>{second}</DisplayString>
<Expand>
<ExpandedItem>second</ExpandedItem>
</Expand>
</Type>
</AutoVisualizer>
File diff suppressed because it is too large Load Diff
+4 -15
View File
@@ -52,10 +52,6 @@
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
#endif
#ifndef JSON_BRACE_INIT_COPY_SEMANTICS
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
@@ -74,27 +70,20 @@
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
#endif
#if JSON_BRACE_INIT_COPY_SEMANTICS
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS _bics
#else
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d) json_abi ## a ## b ## c ## d
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d)
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS)
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
+1 -6
View File
@@ -75,12 +75,7 @@ target_compile_options(test_main PUBLIC
# is annotated JSON_HEDLEY_NO_RETURN (it always throws), which
# makes MSVC flag the code following its call in binary_reader.hpp
# as unreachable for that instantiation, in both Debug and Release
# Disable warning C4503: decorated name length exceeded, name was truncated; the deep
# copy support added for #5387 pushes the mangled name of
# std::allocator_traits<...>::construct for the custom-base-class
# test's map type past VS2015's limit. The name is only used for
# debug info, so truncation does not affect the build.
$<$<CXX_COMPILER_ID:MSVC>:/W4;/wd4566;/wd4996;/wd4702;/wd4503>
$<$<CXX_COMPILER_ID:MSVC>:/W4;/wd4566;/wd4996;/wd4702>
# https://github.com/nlohmann/json/issues/1114
$<$<CXX_COMPILER_ID:MSVC>:/bigobj> $<$<BOOL:${MINGW}>:-Wa,-mbig-obj>
-14
View File
@@ -14,20 +14,6 @@ add_test(
NAME test-abi_config_noversion
COMMAND abi_config_noversion ${DOCTEST_TEST_FILTER})
# test default and no version namespace with all ABI tags enabled, so the
# expected tag order is checked regardless of the JSON_* CMake options
foreach(test default noversion)
add_executable(abi_config_${test}_all_tags ${test}.cpp)
target_compile_definitions(abi_config_${test}_all_tags PRIVATE
JSON_DIAGNOSTICS=1
JSON_DIAGNOSTIC_POSITIONS=1
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1)
target_link_libraries(abi_config_${test}_all_tags PRIVATE abi_compat_main)
add_test(
NAME test-abi_config_${test}_all_tags
COMMAND abi_config_${test}_all_tags ${DOCTEST_TEST_FILTER})
endforeach()
# test custom namespace
add_executable(abi_config_custom custom.cpp)
target_link_libraries(abi_config_custom PRIVATE abi_compat_main)
+2 -6
View File
@@ -24,16 +24,12 @@ TEST_CASE("default namespace")
expected += "_diag";
#endif
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
expected += "_ldvcmp";
#endif
#if JSON_DIAGNOSTIC_POSITIONS
expected += "_dp";
#endif
#if JSON_BRACE_INIT_COPY_SEMANTICS
expected += "_bics";
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
expected += "_ldvcmp";
#endif
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
+2 -6
View File
@@ -25,16 +25,12 @@ TEST_CASE("default namespace without version component")
expected += "_diag";
#endif
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
expected += "_ldvcmp";
#endif
#if JSON_DIAGNOSTIC_POSITIONS
expected += "_dp";
#endif
#if JSON_BRACE_INIT_COPY_SEMANTICS
expected += "_bics";
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
expected += "_ldvcmp";
#endif
expected += "::basic_json";
+21 -15
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.11...3.14)
cmake_minimum_required(VERSION 3.14)
project(JSON_Benchmarks LANGUAGES CXX)
# set compiler flags
@@ -6,29 +6,35 @@ if((CMAKE_CXX_COMPILER_ID MATCHES GNU) OR (CMAKE_CXX_COMPILER_ID MATCHES Clang))
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto -DNDEBUG -O3")
endif()
# configure Google Benchmarks
# configure Google Benchmark; a fixed release, so that results stay comparable
set(JSON_GOOGLE_BENCHMARK_VERSION 1.9.5)
include(FetchContent)
FetchContent_Declare(
benchmark
GIT_REPOSITORY https://github.com/google/benchmark.git
GIT_TAG origin/main
GIT_SHALLOW TRUE
)
FetchContent_GetProperties(benchmark)
if(NOT benchmark_POPULATED)
FetchContent_Populate(benchmark)
set(BENCHMARK_ENABLE_TESTING OFF CACHE INTERNAL "" FORCE)
add_subdirectory(${benchmark_SOURCE_DIR} ${benchmark_BINARY_DIR})
endif()
# only the library is needed; -Werror would break the pinned release as soon as
# a newer compiler adds a warning
set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_WERROR OFF CACHE BOOL "" FORCE)
FetchContent_Declare(benchmark
URL https://github.com/google/benchmark/archive/refs/tags/v${JSON_GOOGLE_BENCHMARK_VERSION}.tar.gz
URL_HASH SHA256=9631341c82bac4a288bef951f8b26b41f69021794184ece969f8473977eaa340
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
FetchContent_MakeAvailable(benchmark)
# download test data
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake ${CMAKE_MODULE_PATH})
include(download_test_data)
# the header to benchmark; point this at a directory holding another version's
# nlohmann/json.hpp to compare versions (see README.md)
set(JSON_BENCHMARK_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../single_include" CACHE PATH
"directory containing the nlohmann/json.hpp to benchmark")
# benchmark binary
add_executable(json_benchmarks src/benchmarks.cpp)
target_compile_features(json_benchmarks PRIVATE cxx_std_11)
target_link_libraries(json_benchmarks benchmark ${CMAKE_THREAD_LIBS_INIT})
add_dependencies(json_benchmarks download_test_data)
target_include_directories(json_benchmarks PRIVATE ${CMAKE_SOURCE_DIR}/../../single_include ${CMAKE_BINARY_DIR}/include)
target_include_directories(json_benchmarks PRIVATE ${JSON_BENCHMARK_INCLUDE_DIR} ${CMAKE_BINARY_DIR}/include)
+130
View File
@@ -0,0 +1,130 @@
# Benchmarks
Micro-benchmarks for parsing, serialization and the binary formats, written with
[Google Benchmark](https://github.com/google/benchmark). They are not run by CI; see
[When to run them](#when-to-run-them).
## What is measured
| benchmark | what it does |
|---|---|
| `ParseFile`, `ParseString` | parse JSON from a file stream or a string |
| `ParseIndented` | parse the large files re-indented by 4 spaces, for the lexer's whitespace handling |
| `Dump` | serialize, compact (`-`) and indented (`4`) |
| `ToCbor`, `BinaryToCbor` | write CBOR; `BinaryToCbor` writes binary values of growing size |
| `FromMsgpack` | read MessagePack; unchanged over the years, so its numbers stay comparable across releases |
| `FromBinaryBuffer`, `FromBinaryFile` | read CBOR, MessagePack, UBJSON, BJData and BSON from a buffer or a `FILE*` |
| `FromBinaryShape` | read deeply nested, container-heavy and scalar-heavy documents in every binary format |
| `FromCborChunkedString` | read CBOR strings split into indefinite-length chunks |
The input files are those of [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) (`canada`,
`citm_catalog`, `twitter`), a large `jeopardy` file, and number-heavy files (`floats`, `signed_ints`, ...).
`bytes_per_second` counts the bytes read or written: the JSON text when parsing, the output when serializing.
## Requirements
- CMake 3.14 or later, a C++11 compiler, and Ninja for the `make` target.
- Network access on the first configure: CMake downloads Google Benchmark and the
[test data](https://github.com/nlohmann/json_test_data) into the build directory. To reuse a download of the test
data, pass `-DJSON_TestDataDirectory=<build directory>/test_files`.
- Google Benchmark is pinned to a release (1.9.5), so that results from different days stay comparable. To update it,
change `JSON_GOOGLE_BENCHMARK_VERSION` and the archive's `URL_HASH` in `CMakeLists.txt` together.
- The benchmarks include `single_include/nlohmann/json.hpp`, so run `make amalgamate` after changing anything in
`include/`.
GCC and Clang builds use `-O3 -flto -DNDEBUG`.
## Running them
From the repository root, this builds everything from scratch in `cmake-build-benchmarks` and runs all benchmarks:
```sh
make run_benchmarks
```
To build once and run selectively:
```sh
cmake -S tests/benchmarks -B build-benchmarks -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build-benchmarks
build-benchmarks/json_benchmarks --benchmark_filter='ParseString|Dump'
```
Useful options of `json_benchmarks`:
| option | effect |
|---|---|
| `--benchmark_list_tests` | list the benchmarks instead of running them |
| `--benchmark_filter=<regex>` | run only the benchmarks whose names match |
| `--benchmark_repetitions=<n>` | run every benchmark `n` times and add mean, median, standard deviation and coefficient of variation |
| `--benchmark_enable_random_interleaving=true` | run the repetitions in random order, which spreads out drifts such as thermal throttling |
| `--benchmark_min_time=<seconds>s` | run each benchmark at least this long (e.g. `2s`) |
| `--benchmark_out=<file> --benchmark_out_format=json` | also write the results to a file, e.g. for `compare.py` |
## Reading the output
Each line shows the wall-clock `Time` and the `CPU` time per iteration, the number of `Iterations` Google Benchmark
chose, and the throughput in `bytes_per_second`. With repetitions, the lines ending in `_median` are the ones to
compare. A `_cv` (coefficient of variation) above a few percent means the machine was too noisy for small
differences to mean anything.
## Comparing two versions
To see what a change or a release did, build the same benchmarks twice: once against the header of the version to
compare with, and once against the current one. `JSON_BENCHMARK_INCLUDE_DIR` names the directory holding the
`nlohmann/json.hpp` to benchmark. For example, to compare the current checkout with 3.12.0:
```sh
# the header of the version to compare with
mkdir -p build-baseline-header/nlohmann
git show v3.12.0:single_include/nlohmann/json.hpp > build-baseline-header/nlohmann/json.hpp
# the same benchmarks, built against either header
cmake -S tests/benchmarks -B build-baseline -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DJSON_BENCHMARK_INCLUDE_DIR="$PWD/build-baseline-header"
cmake -S tests/benchmarks -B build-current -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build-baseline
cmake --build build-current
# run both, back to back
build-baseline/json_benchmarks --benchmark_repetitions=10 --benchmark_enable_random_interleaving=true \
--benchmark_out=build-baseline/results.json --benchmark_out_format=json
build-current/json_benchmarks --benchmark_repetitions=10 --benchmark_enable_random_interleaving=true \
--benchmark_out=build-current/results.json --benchmark_out_format=json
```
Google Benchmark ships a tool to compare the two result files. It needs NumPy and SciPy:
```sh
python3 -m venv build-venv
build-venv/bin/pip install numpy scipy
build-venv/bin/python build-current/_deps/benchmark-src/tools/compare.py -a benchmarks build-baseline/results.json build-current/results.json
```
The tool's own `tools/requirements.txt` pins NumPy and SciPy versions that need Python 3.11 or later; with an older
Python, unpinned versions work as well. In its output:
- the `Time` and `CPU` columns are relative changes: `-0.35` means 35% faster, `+0.10` means 10% slower;
- `_pvalue` lines report a Mann-Whitney U test of whether the two versions differ. It needs at least 9
repetitions, and a p-value below 0.05 means the difference is unlikely to be noise;
- `OVERALL_GEOMEAN` summarizes all benchmarks;
- `-a` shows only the aggregates, not every repetition.
The header you compare with must support everything the benchmarks use. The current benchmarks build against 3.12.0.
Only benchmarks present in both result files are compared, so for older releases, either filter the benchmarks or
build that release's own `tests/benchmarks` against its own header.
## Getting stable numbers
- Build and run both versions on the same machine, one right after the other.
- Keep the machine otherwise idle: no builds, no browser, and a laptop plugged in.
- On Linux, set the CPU frequency governor to `performance`, e.g. `sudo cpupower frequency-set --governor performance`.
Google Benchmark prints a warning when frequency scaling is enabled. Pinning the process to a core
(`taskset -c 2 ...`) helps as well.
- Use 10 or more repetitions with random interleaving, compare medians, and treat changes within the `_cv` as noise.
## When to run them
They are a manual step, not part of CI: shared CI runners vary more between runs than most of the effects measured.
Run the comparison above before a release, comparing the previous release tag with `develop`, and for pull requests
that claim to change performance.
+2 -1
View File
@@ -131,7 +131,8 @@ static void Dump(benchmark::State& state, const char* filename, int indent)
while (state.KeepRunning())
{
j.dump(indent);
std::string output = j.dump(indent);
benchmark::DoNotOptimize(output);
}
state.SetBytesProcessed(state.iterations() * j.dump(indent).size());
-6
View File
@@ -46,16 +46,10 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
#ifdef NDEBUG
#error "the fuzzer drivers must be built without NDEBUG"
#endif
using json = nlohmann::json;
// value-stable comparison for the round-trip checks below; see the note
-6
View File
@@ -19,16 +19,10 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
#ifdef NDEBUG
#error "the fuzzer drivers must be built without NDEBUG"
#endif
using json = nlohmann::json;
// see http://llvm.org/docs/LibFuzzer.html
-6
View File
@@ -19,16 +19,10 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
#ifdef NDEBUG
#error "the fuzzer drivers must be built without NDEBUG"
#endif
using json = nlohmann::json;
// see http://llvm.org/docs/LibFuzzer.html
-6
View File
@@ -20,16 +20,10 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
#ifdef NDEBUG
#error "the fuzzer drivers must be built without NDEBUG"
#endif
using json = nlohmann::json;
// see http://llvm.org/docs/LibFuzzer.html
-6
View File
@@ -19,16 +19,10 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
#ifdef NDEBUG
#error "the fuzzer drivers must be built without NDEBUG"
#endif
using json = nlohmann::json;
// see http://llvm.org/docs/LibFuzzer.html
-6
View File
@@ -25,16 +25,10 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers.
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
// the round-trip checks below are assertions; NDEBUG would compile them away
#ifdef NDEBUG
#error "the fuzzer drivers must be built without NDEBUG"
#endif
using json = nlohmann::json;
// see http://llvm.org/docs/LibFuzzer.html
-51
View File
@@ -216,57 +216,6 @@ TEST_CASE("controlled bad_alloc")
CHECK_THROWS_AS(my_json(s), std::bad_alloc&);
next_construct_fails = false;
}
SECTION("basic_json(const basic_json&) of a deeply nested value (#5387)")
{
// Copying a value nested deeper than the descent bound builds the
// copy from the top down: every value whose own copy has not been
// made yet stays a null value until it is. Failing an allocation
// part-way through is what proves such a half-built copy can still
// be destroyed.
//
// Which path the failure lands in depends on the build: the first
// allocation of a copy belongs to the outermost level, so here it
// is the descending one. Built with JSON_NO_THREAD_LOCAL - as the
// ci_test_no_thread_local target builds the whole suite - no
// descent is made at all and the very same failure lands in the
// iterative path instead, part-way through its worklist.
const auto check_deep_copy = [](bool objects)
{
CAPTURE(objects);
next_construct_fails = false;
// deeper than the 128 levels the copy constructor descends into
const std::size_t depth = 300;
my_json j = 1;
for (std::size_t i = 0; i < depth; ++i)
{
if (objects)
{
my_json wrapper = my_json::object();
wrapper["a"] = std::move(j);
j = std::move(wrapper);
}
else
{
j = my_json::array({std::move(j)});
}
}
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization): the copy is what is tested
CHECK_NOTHROW(my_json(j));
next_construct_fails = true;
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization): the copy is what is tested
CHECK_THROWS_AS(my_json(j), std::bad_alloc&);
next_construct_fails = false;
};
check_deep_copy(false);
check_deep_copy(true);
}
}
}
+19 -93
View File
@@ -2604,25 +2604,25 @@ TEST_CASE("BJData")
// that still round-trips.
// string data declared as a uint64 array
json const j_str = json({{"_ArrayType_", "uint64"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {"pointer", "value"}}});
json const j_str = json({{"_ArrayType_", "uint64"}, {"_ArraySize_", {1}}, {"_ArrayData_", {"pointer"}}});
const auto out_str = json::to_bjdata(j_str);
CHECK(out_str.at(0) == '{');
CHECK(json::from_bjdata(out_str) == j_str);
// integer data declared as a double array
json const j_float = json({{"_ArrayType_", "double"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1, 2}}});
json const j_float = json({{"_ArrayType_", "double"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 2}}});
const auto out_float = json::to_bjdata(j_float);
CHECK(out_float.at(0) == '{');
CHECK(json::from_bjdata(out_float) == j_float);
// a non-integer shape entry is likewise not treated as an ndarray
json const j_size = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {"x", 1}}, {"_ArrayData_", {1}}});
json const j_size = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {"x"}}, {"_ArrayData_", {1}}});
const auto out_size = json::to_bjdata(j_size);
CHECK(out_size.at(0) == '{');
CHECK(json::from_bjdata(out_size) == j_size);
// a negative shape entry is not a usable dimension either
json const j_neg = json::parse(R"({"_ArrayType_":"uint8","_ArraySize_":[-1,1],"_ArrayData_":[1]})");
json const j_neg = json::parse(R"({"_ArrayType_":"uint8","_ArraySize_":[-1],"_ArrayData_":[1]})");
const auto out_neg = json::to_bjdata(j_neg);
CHECK(out_neg.at(0) == '{');
CHECK(json::from_bjdata(out_neg) == j_neg);
@@ -2657,14 +2657,14 @@ TEST_CASE("BJData")
}
// negative values under a signed type behave the same way
const auto from_neg = json::to_bjdata(json::parse(R"({"_ArrayType_":"int32","_ArraySize_":[2,1],"_ArrayData_":[-5,7]})"));
const auto from_neg = json::to_bjdata(json::parse(R"({"_ArrayType_":"int32","_ArraySize_":[2],"_ArrayData_":[-5,7]})"));
CHECK(from_neg.at(0) == '[');
CHECK(from_neg == json::to_bjdata(json({{"_ArrayType_", "int32"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {-5, 7}}})));
CHECK(from_neg == json::to_bjdata(json({{"_ArrayType_", "int32"}, {"_ArraySize_", {2}}, {"_ArrayData_", {-5, 7}}})));
// and so do the floating point types
const auto from_float = json::to_bjdata(json::parse(R"({"_ArrayType_":"double","_ArraySize_":[2,1],"_ArrayData_":[1.5,2.5]})"));
const auto from_float = json::to_bjdata(json::parse(R"({"_ArrayType_":"double","_ArraySize_":[2],"_ArrayData_":[1.5,2.5]})"));
CHECK(from_float.at(0) == '[');
CHECK(from_float == json::to_bjdata(json({{"_ArrayType_", "double"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5, 2.5}}})));
CHECK(from_float == json::to_bjdata(json({{"_ArrayType_", "double"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1.5, 2.5}}})));
}
SECTION("optimized ndarray (type and vector-size as 1D array)")
@@ -2835,7 +2835,7 @@ TEST_CASE("BJData")
// a single dimension that does not fit into std::size_t is
// rejected for the same reason (only observable where
// std::size_t is narrower than 64 bit)
json j_huge = json({{"_ArrayData_", json::array()}, {"_ArraySize_", {18446744073709551615ull, 2}}, {"_ArrayType_", "uint8"}});
json j_huge = json({{"_ArrayData_", json::array()}, {"_ArraySize_", {18446744073709551615ull}}, {"_ArrayType_", "uint8"}});
CHECK(json::from_bjdata(json::to_bjdata(j_huge), true, true) == j_huge);
// a well-formed ndarray is still encoded as one
@@ -2878,116 +2878,42 @@ TEST_CASE("BJData")
// object encoding that still round-trips (see GitHub issue #5403)
// an unsigned element that does not fit uint8
json const j_uint8 = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1, 256}}});
json const j_uint8 = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 256}}});
const auto out_uint8 = json::to_bjdata(j_uint8);
CHECK(out_uint8.at(0) == '{');
CHECK(json::from_bjdata(out_uint8) == j_uint8);
// a signed element that does not fit int8
json const j_int8 = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1, 200}}});
json const j_int8 = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 200}}});
const auto out_int8 = json::to_bjdata(j_int8);
CHECK(out_int8.at(0) == '{');
CHECK(json::from_bjdata(out_int8) == j_int8);
// a negative element is likewise out of range for an
// unsigned _ArrayType_
json const j_uint16_neg = json({{"_ArrayType_", "uint16"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1, -1}}});
json const j_uint16_neg = json({{"_ArrayType_", "uint16"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, -1}}});
const auto out_uint16_neg = json::to_bjdata(j_uint16_neg);
CHECK(out_uint16_neg.at(0) == '{');
CHECK(json::from_bjdata(out_uint16_neg) == j_uint16_neg);
// a double element that overflows to infinity when narrowed
// to the "single" (float) precision named by _ArrayType_
json const j_single = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5, 1e40}}});
json const j_single = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1.5, 1e40}}});
const auto out_single = json::to_bjdata(j_single);
CHECK(out_single.at(0) == '{');
CHECK(json::from_bjdata(out_single) == j_single);
// in-range boundary values still use the compact ndarray encoding
json const j_uint8_ok = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {0, 255}}});
CHECK(json::to_bjdata(j_uint8_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, 'i', 1, ']', 0, 255}));
json const j_uint8_ok = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {0, 255}}});
CHECK(json::to_bjdata(j_uint8_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, ']', 0, 255}));
json const j_int8_ok = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {-128, 127}}});
CHECK(json::to_bjdata(j_int8_ok) == std::vector<uint8_t>({'[', '$', 'i', '#', '[', 'i', 2, 'i', 1, ']', 0x80, 0x7F}));
json const j_int8_ok = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {-128, 127}}});
CHECK(json::to_bjdata(j_int8_ok) == std::vector<uint8_t>({'[', '$', 'i', '#', '[', 'i', 2, ']', 0x80, 0x7F}));
json const j_single_ok = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5, -1.5}}});
json const j_single_ok = json({{"_ArrayType_", "single"}, {"_ArraySize_", {1}}, {"_ArrayData_", {1.5}}});
const auto out_single_ok = json::to_bjdata(j_single_ok);
CHECK(out_single_ok.at(0) == '[');
CHECK(json::from_bjdata(out_single_ok) == json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5f, -1.5f}}}));
}
SECTION("ndarray that would not be read back as an annotated object stays as object")
{
// the reader only restores an annotated object from an ND-array
// with at least two non-zero dimensions that is not a 1xN row
// vector; any other shape is read back as a plain array. Writing
// such an object as an ND-array would drop its annotation, so it
// falls back to a plain object encoding that round-trips.
for (const char* text :
{
R"({"_ArrayType_":"int16","_ArraySize_":[],"_ArrayData_":[]})",
R"({"_ArrayType_":"int16","_ArraySize_":[2],"_ArrayData_":[1,2]})",
R"({"_ArrayType_":"int16","_ArraySize_":[1,2],"_ArrayData_":[1,2]})",
R"({"_ArrayType_":"int16","_ArraySize_":[0],"_ArrayData_":[]})",
R"({"_ArrayType_":"int16","_ArraySize_":[2,0],"_ArrayData_":[]})",
R"({"_ArrayType_":"int16","_ArraySize_":[0,2],"_ArrayData_":[]})"
})
{
CAPTURE(text);
const json j = json::parse(text);
for (const bool use_size :
{
false, true
})
{
const auto out = json::to_bjdata(j, use_size, use_size);
CHECK(out.at(0) == '{');
CHECK(json::from_bjdata(out) == j);
}
}
// a genuine ND-array still uses the compact encoding and round-trips
const json j_2d = json::parse(R"({"_ArrayType_":"int16","_ArraySize_":[2,1],"_ArrayData_":[1,2]})");
const auto out_2d = json::to_bjdata(j_2d);
CHECK(out_2d.at(0) == '[');
CHECK(json::from_bjdata(out_2d) == j_2d);
}
SECTION("ndarray with non-array _ArrayData_ stays as object")
{
// the elements are written from _ArrayData_ as a flat list, so it
// has to be an array: null has size 0, any other scalar has size 1,
// and iterating an object visits its values, so each of these could
// match the dimensions and be encoded as an unrelated ND-array
for (const char* text :
{
R"({"_ArrayType_":"int16","_ArraySize_":[2,1],"_ArrayData_":null})",
R"({"_ArrayType_":"int16","_ArraySize_":[2,1],"_ArrayData_":{"a":1,"b":2}})",
R"({"_ArrayType_":"int16","_ArraySize_":[1],"_ArrayData_":5})",
R"({"_ArrayType_":"int16","_ArraySize_":[],"_ArrayData_":null})"
})
{
CAPTURE(text);
const json j = json::parse(text);
const auto out = json::to_bjdata(j);
CHECK(out.at(0) == '{');
CHECK(json::from_bjdata(out) == j);
}
// OSS-Fuzz issue 563659413: an empty binary _ArraySize_ is written
// as a plain object and read back as an empty array, after which
// the object with a null _ArrayData_ was encoded as an empty
// ND-array and re-read as [], so a second round trip lost the value
const std::vector<uint8_t> input =
{
'{', 'U', 11, '_', 'A', 'r', 'r', 'a', 'y', 'D', 'a', 't', 'a', '_', 'Z',
'U', 11, '_', 'A', 'r', 'r', 'a', 'y', 'T', 'y', 'p', 'e', '_', 'S', 'i', 5, 'i', 'n', 't', '1', '6',
'U', 11, '_', 'A', 'r', 'r', 'a', 'y', 'S', 'i', 'z', 'e', '_', '[', '$', 'B', '#', '[', ']', '}'
};
const json j1 = json::from_bjdata(input);
const json j2 = json::from_bjdata(json::to_bjdata(j1, false, false));
CHECK(j2 == json::parse(R"({"_ArrayType_":"int16","_ArraySize_":[],"_ArrayData_":null})"));
CHECK(json::from_bjdata(json::to_bjdata(j2, false, false)) == j2);
CHECK(json::from_bjdata(out_single_ok) == json({1.5f}));
}
SECTION("ndarray with _ArrayType_ \"byte\" is gated by the BJData draft version")
@@ -1,167 +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_BRACE_INIT_COPY_SEMANTICS, so it defines the
// macro itself rather than relying on a -D flag, and runs in every build.
#ifdef JSON_BRACE_INIT_COPY_SEMANTICS
#undef JSON_BRACE_INIT_COPY_SEMANTICS
#endif
#define JSON_BRACE_INIT_COPY_SEMANTICS 1
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <array>
#include <list>
#include <map>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#define STRINGIZE_EX(x) #x
#define STRINGIZE(x) STRINGIZE_EX(x)
TEST_CASE("JSON_BRACE_INIT_COPY_SEMANTICS")
{
SECTION("the macro is part of the ABI tag")
{
const std::string ns = STRINGIZE(NLOHMANN_JSON_NAMESPACE);
// other tags may come before it, e.g. json_abi_ldvcmp_bics
CHECK(ns.find("_bics") != std::string::npos);
}
SECTION("single-element brace initialization copies the element (#5074)")
{
json const j_obj = {{"key", "value"}, {"num", 42}};
json const j_arr = {1, 2, 3};
// object: brace init copies instead of wrapping
json const j1{j_obj};
CHECK(j1.is_object());
CHECK(j1 == j_obj);
// array: brace init copies instead of wrapping
json const j2{j_arr};
CHECK(j2.is_array());
CHECK(j2.size() == 3);
CHECK(j2 == j_arr);
// this applies to any single element, not only to JSON values
json const j3{true};
CHECK(j3.is_boolean());
json const j4{42};
CHECK(j4.is_number_integer());
json const j5 = {1};
CHECK(j5 == 1);
json const j6 = {"text"};
CHECK(j6 == "text");
json const j7 = {{1, 2}};
CHECK(j7 == json::array({1, 2}));
}
SECTION("what the macro does not change")
{
// lists with more than one element are unaffected
json const j1 = {1, 2};
CHECK(j1.is_array());
CHECK(j1.size() == 2);
// a single [string, value] pair still describes an object
json const j2 = {{"key", "value"}};
CHECK(j2.is_object());
CHECK(j2["key"] == "value");
// json::array() always creates an array
json const j3 = json::array({1});
CHECK(j3.is_array());
CHECK(j3.size() == 1);
CHECK(j3[0] == 1);
json const j_obj = {{"key", "value"}};
json const j4 = json::array({j_obj});
CHECK(j4.is_array());
CHECK(j4.size() == 1);
CHECK(j4[0] == j_obj);
}
SECTION("conversions build the same values as without the macro")
{
SECTION("one-element std::tuple")
{
json const j1 = std::tuple<int> {5};
CHECK(j1.dump() == "[5]");
CHECK(std::get<0>(j1.get<std::tuple<int>>()) == 5);
json const j2 = std::tuple<std::string> {"text"};
CHECK(j2.dump() == "[\"text\"]");
CHECK(std::get<0>(j2.get<std::tuple<std::string>>()) == "text");
json const j3 = std::tuple<json> {json::array({1, 2})};
CHECK(j3.dump() == "[[1,2]]");
// as without the macro, a [string, value] pair becomes an object
// member (see the known limitation documented for std::pair)
json const j4 = std::tuple<std::pair<std::string, int>> {{"a", 1}};
CHECK(j4.dump() == "{\"a\":1}");
}
SECTION("tuples with more elements")
{
json const j1 = std::tuple<int, std::string> {1, "a"};
CHECK(j1.dump() == "[1,\"a\"]");
json const j2 = std::tuple<> {};
CHECK(j2.dump() == "[]");
}
SECTION("one-element containers")
{
json const j1 = std::vector<int> {1};
CHECK(j1.dump() == "[1]");
CHECK(j1.get<std::vector<int>>() == std::vector<int> {1});
std::array<int, 1> const arr = {{1}};
json const j2 = arr;
CHECK(j2.dump() == "[1]");
json const j3 = std::list<std::string> {"a"};
CHECK(j3.dump() == "[\"a\"]");
json const j4 = std::map<std::string, int> {{"a", 1}};
CHECK(j4.dump() == "{\"a\":1}");
json const j5 = std::map<int, int> {{1, 2}};
CHECK(j5.dump() == "[[1,2]]");
}
SECTION("std::pair")
{
json const j = std::pair<int, int> {1, 2};
CHECK(j.dump() == "[1,2]");
CHECK((j.get<std::pair<int, int>>() == std::pair<int, int> {1, 2}));
}
SECTION("items()")
{
json j_obj = {{"key", 1}};
for (const auto& el : j_obj.items())
{
json const j = el;
CHECK(j.dump() == "{\"key\":1}");
}
}
}
}
-66
View File
@@ -75,72 +75,6 @@ TEST_CASE("Better diagnostics with positions")
CHECK(j.end_pos() == root.size());
}
SECTION("copying keeps the positions of nested values (#5387)")
{
// Values nested deeper than the copy constructor's descent bound are
// copied without the call stack, on a path that has to carry the
// positions over itself; shallower ones copy their containers, which
// bring the positions along. Both sides of the bound are checked here.
const auto check_copy = [](std::size_t depth, bool objects)
{
CAPTURE(depth)
CAPTURE(objects)
const std::string opening = objects ? R"({"a":)" : "[";
const std::string closing = objects ? "}" : "]";
std::string text;
for (std::size_t i = 0; i < depth; ++i)
{
text += opening;
}
text += "12";
for (std::size_t i = 0; i < depth; ++i)
{
text += closing;
}
const json original = json::parse(text);
const json copy(original); // NOLINT(performance-unnecessary-copy-initialization)
const json* o = &original;
const json* c = &copy;
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)
{
o = objects ? &o->at("a") : &o->at(0);
c = objects ? &c->at("a") : &c->at(0);
}
}
};
const auto check_arrays = [&check_copy](std::size_t depth)
{
check_copy(depth, false);
};
const auto check_objects = [&check_copy](std::size_t depth)
{
check_copy(depth, true);
};
check_arrays(1);
check_arrays(127);
check_arrays(128);
check_arrays(129);
check_arrays(300);
check_objects(1);
check_objects(127);
check_objects(128);
check_objects(129);
check_objects(300);
}
SECTION("JSON patch add to primitive parent (#4292)")
{
// the JSON Patch "add" target /foo/bar/baz has a string parent
-106
View File
@@ -274,63 +274,6 @@ TEST_CASE("Regression tests for extended diagnostics")
CHECK(j1["string"] == "t");
}
SECTION("Regression test for issue #5387 - copying keeps the parents of nested values")
{
// A value nested deeper than the copy constructor's descent bound is
// copied 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.
const std::size_t depth = 300;
SECTION("objects")
{
json j = "not a number";
std::string pointer;
for (std::size_t i = 0; i < depth; ++i)
{
j = json{{"a", j}};
pointer += "/a";
}
json const copy(j); // NOLINT(performance-unnecessary-copy-initialization)
const json* inner = &copy;
for (std::size_t i = 0; i < depth; ++i)
{
inner = &inner->at("a");
}
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(), json::type_error);
CHECK(i == 0);
}
SECTION("arrays")
{
json j = "not a number";
std::string pointer;
for (std::size_t i = 0; i < depth; ++i)
{
j = json::array({j});
pointer += "/0";
}
json const copy(j); // NOLINT(performance-unnecessary-copy-initialization)
const json* inner = &copy;
for (std::size_t i = 0; i < depth; ++i)
{
inner = &inner->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(), json::type_error);
CHECK(i == 0);
}
}
SECTION("Regression test - swap(array_t&)/swap(object_t&) must update JSON_DIAGNOSTICS parent pointers")
{
// swap(array_t&)
@@ -363,52 +306,3 @@ TEST_CASE("Regression tests for extended diagnostics")
}
}
TEST_CASE("Better diagnostics past the descent bound of update() and merge_patch()")
{
// Both merge objects nested more than detail::recursion_depth_limit()
// (128) levels deep without recursing; the values they add or replace
// there must still know their parents.
// The values are built rather than parsed, so that the expected messages
// carry no byte positions under JSON_DIAGNOSTIC_POSITIONS.
const std::size_t depth = 200;
json target = {{"x", 1}};
json patch = {{"y", 2}};
std::string path;
for (std::size_t i = 0; i < depth; ++i)
{
target = json{{"a", std::move(target)}};
patch = json{{"a", std::move(patch)}};
path += "/a";
}
const std::string expected_x = "[json.exception.type_error.304] (" + path + "/x) cannot use at() with number";
const std::string expected_y = "[json.exception.type_error.304] (" + path + "/y) cannot use at() with number";
SECTION("update()")
{
json j = target;
j.update(patch, true);
// walk down through const references, which leave m_parent alone
const json* p = &j;
for (std::size_t i = 0; i < depth; ++i)
{
p = &p->at("a");
}
CHECK_THROWS_WITH_AS(p->at("x").at(0), expected_x.c_str(), json::type_error);
CHECK_THROWS_WITH_AS(p->at("y").at(0), expected_y.c_str(), json::type_error);
}
SECTION("merge_patch()")
{
json j = target;
j.merge_patch(patch);
const json* p = &j;
for (std::size_t i = 0; i < depth; ++i)
{
p = &p->at("a");
}
CHECK_THROWS_WITH_AS(p->at("x").at(0), expected_x.c_str(), json::type_error);
CHECK_THROWS_WITH_AS(p->at("y").at(0), expected_y.c_str(), json::type_error);
}
}
-113
View File
@@ -13,78 +13,6 @@ using json = nlohmann::json;
using ordered_json = nlohmann::ordered_json;
#include <set>
#include <string>
namespace
{
// how detail::hash defines the hash of an array or object: the seeds of the
// elements, combined in order. Recursive, so only usable on values nested a
// few hundred levels deep - which is exactly what is needed to check that the
// iterative path taken below detail::recursion_depth_limit() computes the same.
template<typename BasicJsonType>
std::size_t reference_hash(const BasicJsonType& j)
{
using nlohmann::detail::combine;
using string_t = typename BasicJsonType::string_t;
if (!j.is_structured())
{
return std::hash<BasicJsonType> {}(j);
}
auto seed = combine(static_cast<std::size_t>(j.type()), j.size());
for (const auto& element : j.items())
{
if (j.is_object())
{
seed = combine(seed, std::hash<string_t> {}(element.key()));
}
seed = combine(seed, reference_hash(element.value()));
}
return seed;
}
// a value nested `depth` levels deep, with siblings on every level
template<typename BasicJsonType>
BasicJsonType nested(const std::size_t depth, const bool objects)
{
BasicJsonType value = "leaf";
for (std::size_t i = 0; i < depth; ++i)
{
if (objects)
{
value = BasicJsonType{{"before", i}, {"nested", std::move(value)}, {"after", {i, "x"}}};
}
else
{
value = BasicJsonType::array({i, std::move(value), BasicJsonType::object({{"k", i}})});
}
}
return value;
}
std::string nested_text(const std::size_t depth, const bool objects)
{
std::string text;
if (objects)
{
text.reserve((6 * depth) + 1);
for (std::size_t i = 0; i < depth; ++i)
{
text += "{\"a\":";
}
text += "1";
text.append(depth, '}');
}
else
{
text.assign(depth, '[');
text += "1";
text.append(depth, ']');
}
return text;
}
} // namespace
TEST_CASE("hash<nlohmann::json>")
{
@@ -183,44 +111,3 @@ TEST_CASE("hash<nlohmann::ordered_json>")
CHECK(hashes.size() == 21);
}
TEST_CASE("hash of deeply nested values")
{
SECTION("hashing past the descent bound computes the same values")
{
// every depth on either side of where the iterative path takes over
for (std::size_t depth = 0; depth <= (2 * nlohmann::detail::recursion_depth_limit()) + 10; ++depth)
{
CAPTURE(depth);
const auto arrays = nested<json>(depth, false);
const auto objects = nested<json>(depth, true);
const auto ordered = nested<ordered_json>(depth, true);
CHECK(std::hash<json> {}(arrays) == reference_hash(arrays));
CHECK(std::hash<json> {}(objects) == reference_hash(objects));
CHECK(std::hash<ordered_json> {}(ordered) == reference_hash(ordered));
}
}
SECTION("values nested too deeply for the call stack (#5545)")
{
// recursing once per level used to exhaust the call stack here; the
// values are only parsed and hashed, never copied or compared, since
// those recurse as well
const std::size_t depth = 100000;
for (const bool objects :
{
false, true
})
{
CAPTURE(objects);
const auto text = nested_text(depth, objects);
const auto a = json::parse(text);
const auto b = json::parse(text);
CHECK(std::hash<json> {}(a) == std::hash<json> {}(b));
const auto c = ordered_json::parse(text);
const auto d = ordered_json::parse(text);
CHECK(std::hash<ordered_json> {}(c) == std::hash<ordered_json> {}(d));
}
}
}
-151
View File
@@ -12,7 +12,6 @@
using nlohmann::json;
#include <algorithm>
#include <string>
TEST_CASE("tests on very large JSONs")
{
@@ -28,153 +27,3 @@ TEST_CASE("tests on very large JSONs")
}
}
namespace
{
// Descend a chain of single-element containers and return the value at its end,
// reporting the number of levels traversed in @a depth.
//
// The values in the test case below are nested far deeper than the call stack
// can follow, so they must not be inspected with operator== or dump(): both are
// still recursive and would overflow the stack themselves.
const json* innermost_value(const json& j, std::size_t& depth)
{
const json* current = &j;
depth = 0;
while ((current->is_array() || current->is_object()) && !current->empty())
{
current = current->is_array()
? &current->front()
: &current->begin().value();
++depth;
}
return current;
}
} // namespace
TEST_CASE("tests on deeply nested JSONs")
{
// deep enough to exhaust the call stack, but small enough to stay cheap:
// parsing is iterative, so building the values below costs little
const std::size_t depth = 100000;
SECTION("issue #5387 - stack overflow in the copy constructor")
{
SECTION("array")
{
const json j = json::parse(std::string(depth, '[') + '0' + std::string(depth, ']'));
const json copy(j); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested
std::size_t copy_depth = 0;
CHECK(*innermost_value(copy, copy_depth) == 0);
CHECK(copy_depth == depth);
}
SECTION("object")
{
std::string s;
s.reserve((6 * depth) + 1);
for (std::size_t i = 0; i < depth; ++i)
{
s += "{\"a\":";
}
s += '1';
s.append(depth, '}');
const json j = json::parse(s);
const json copy(j); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested
std::size_t copy_depth = 0;
CHECK(*innermost_value(copy, copy_depth) == 1);
CHECK(copy_depth == depth);
}
SECTION("copy assignment")
{
// operator=(basic_json) takes its argument by value, so the deep
// copy happens in the copy constructor
const json j = json::parse(std::string(depth, '[') + '0' + std::string(depth, ']'));
json target;
target = j;
std::size_t target_depth = 0;
CHECK(*innermost_value(target, target_depth) == 0);
CHECK(target_depth == depth);
}
SECTION("depths around the bound of the recursive descent")
{
// The copy constructor descends into a bounded number of levels and
// completes whatever is below that without the call stack. Cover
// every depth around that bound, so that the two ways of copying
// are known to meet cleanly - wherever the bound is set.
for (std::size_t d = 1; d <= 300; ++d)
{
CAPTURE(d);
const json array = json::parse(std::string(d, '[') + '0' + std::string(d, ']'));
const json array_copy(array); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested
std::size_t array_depth = 0;
CHECK(*innermost_value(array_copy, array_depth) == 0);
CHECK(array_depth == d);
std::string object_text;
for (std::size_t i = 0; i < d; ++i)
{
object_text += "{\"a\":";
}
object_text += '1';
object_text.append(d, '}');
const json object = json::parse(object_text);
const json object_copy(object); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested
std::size_t object_depth = 0;
CHECK(*innermost_value(object_copy, object_depth) == 1);
CHECK(object_depth == d);
}
}
SECTION("a value that is deep in one place only")
{
json j = json::object();
j["shallow"] = 1;
j["deep"] = json::parse(std::string(depth, '[') + '0' + std::string(depth, ']'));
j["also_shallow"] = json::array({1, 2, 3});
const json copy(j);
CHECK(copy["shallow"] == 1);
CHECK(copy["also_shallow"] == json::array({1, 2, 3}));
std::size_t deep_depth = 0;
CHECK(*innermost_value(copy["deep"], deep_depth) == 0);
CHECK(deep_depth == depth);
}
SECTION("the copy is independent of the original")
{
const json j = json::parse(std::string(depth, '[') + '0' + std::string(depth, ']'));
json copy(j);
// reach the innermost value without recursing and replace it
json* current = &copy;
while (current->is_array() && !current->empty())
{
current = &current->front();
}
*current = 42;
std::size_t unused = 0;
CHECK(*innermost_value(copy, unused) == 42);
CHECK(*innermost_value(j, unused) == 0);
}
}
}
-103
View File
@@ -14,60 +14,6 @@ using nlohmann::json;
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
#endif
#include <string>
namespace
{
// RFC 7396's MergePatch, written recursively as in the RFC; only usable on
// values nested a few hundred levels deep
void reference_merge_patch(json& target, const json& patch)
{
if (!patch.is_object())
{
target = patch;
return;
}
if (!target.is_object())
{
target = json::object();
}
for (auto it = patch.begin(); it != patch.end(); ++it)
{
if (it.value().is_null())
{
target.erase(it.key());
}
else
{
reference_merge_patch(target[it.key()], it.value());
}
}
}
// objects nested `depth` levels deep under the key "a", with members that
// differ by `variant` on the way down
std::string nested_objects(const std::size_t depth, const int variant)
{
std::string text;
for (std::size_t i = 0; i < depth; ++i)
{
text += "{";
if ((i + static_cast<std::size_t>(variant)) % 3 == 0)
{
text += "\"s" + std::to_string(variant) + "\":" + std::to_string(i) + ",";
}
if (variant == 2 && i % 5 == 0)
{
text += "\"s0\":null,";
}
text += "\"a\":";
}
text += variant == 1 ? "{\"x\":1,\"y\":null}" : "{\"y\":2}";
text.append(depth, '}');
return text;
}
} // namespace
TEST_CASE("JSON Merge Patch")
{
SECTION("examples from RFC 7396")
@@ -296,52 +242,3 @@ TEST_CASE("JSON Merge Patch")
}
}
}
TEST_CASE("JSON Merge Patch on deeply nested values")
{
SECTION("patching past the descent bound gives the same result")
{
// every depth on either side of where the iterative version takes
// over (detail::recursion_depth_limit(), 128)
for (std::size_t depth = 0; depth <= 300; ++depth)
{
CAPTURE(depth);
for (int variant = 0; variant < 3; ++variant)
{
CAPTURE(variant);
const json patch = json::parse(nested_objects(depth, variant));
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
json expected = result;
result.merge_patch(patch);
reference_merge_patch(expected, patch);
CHECK(result == expected);
// a target that is not an object, and an empty one
json from_null;
from_null.merge_patch(patch);
json expected_from_null;
reference_merge_patch(expected_from_null, patch);
CHECK(from_null == expected_from_null);
}
}
}
SECTION("patches nested too deeply for the call stack (#5393)")
{
// applying a patch used to recurse once per nesting level. The result
// is only walked, never copied or compared, since those recurse too.
const std::size_t depth = 100000;
json target = json::parse(nested_objects(depth, 0));
target.merge_patch(json::parse(nested_objects(depth, 1)));
const json* p = &target;
for (std::size_t i = 0; i < depth; ++i)
{
p = &p->at("a");
}
// {"y":2} patched with {"x":1,"y":null}
CHECK(p->size() == 1);
CHECK(p->at("x") == 1);
}
}
-88
View File
@@ -11,53 +11,6 @@
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <string>
namespace
{
// update(source, true) as documented, written recursively; only usable on
// values nested a few hundred levels deep
void reference_update(json& target, const json& source)
{
for (auto it = source.begin(); it != source.end(); ++it)
{
const auto existing = target.find(it.key());
if (it.value().is_object() && existing != target.end() && existing->is_object())
{
reference_update(*existing, it.value());
}
else
{
target[it.key()] = it.value();
}
}
}
// objects nested `depth` levels deep under the key "a", with members that
// differ by `variant` on the way down
std::string nested_objects(const std::size_t depth, const int variant)
{
std::string text;
for (std::size_t i = 0; i < depth; ++i)
{
text += "{";
if ((i + static_cast<std::size_t>(variant)) % 3 == 0)
{
text += "\"s" + std::to_string(variant) + "\":" + std::to_string(i) + ",";
}
if (variant == 2 && i % 5 == 0)
{
// an object replacing a primitive, which is not merged
text += "\"s0\":{\"o\":1},";
}
text += "\"a\":";
}
text += variant == 1 ? "{\"x\":1}" : "{\"y\":2}";
text.append(depth, '}');
return text;
}
} // namespace
TEST_CASE("modifiers")
{
SECTION("clear()")
@@ -1035,44 +988,3 @@ TEST_CASE("modifiers")
}
}
}
TEST_CASE("update() on deeply nested values")
{
SECTION("merging past the descent bound gives the same result")
{
// every depth on either side of where the iterative version takes
// over (detail::recursion_depth_limit(), 128)
for (std::size_t depth = 0; depth <= 300; ++depth)
{
CAPTURE(depth);
for (int variant = 0; variant < 3; ++variant)
{
CAPTURE(variant);
const json source = json::parse(nested_objects(depth, variant));
json result = json::parse(nested_objects(depth, (variant + 1) % 3));
json expected = result;
result.update(source, true);
reference_update(expected, source);
CHECK(result == expected);
}
}
}
SECTION("objects nested too deeply for the call stack (#5545)")
{
// merging used to recurse once per nesting level. The result is only
// walked, never copied or compared, since those recurse too.
const std::size_t depth = 100000;
json target = json::parse(nested_objects(depth, 0));
target.update(json::parse(nested_objects(depth, 1)), true);
const json* p = &target;
for (std::size_t i = 0; i < depth; ++i)
{
p = &p->at("a");
}
CHECK(p->size() == 2);
CHECK(p->at("x") == 1);
CHECK(p->at("y") == 2);
}
}
-34
View File
@@ -82,40 +82,6 @@ TEST_CASE("regression test for issue #3732 - iteration_proxy_value<iter_impl<ord
static_cast<void>(fn);
}
TEST_CASE("copying an ordered_json with nested values")
{
// ordered_map is backed by a vector, so copying an object that has
// structured values takes a different route than copying a std::map-backed
// one; see https://github.com/nlohmann/json/issues/5387
ordered_json oj;
oj["z"] = 1;
oj["a"]["y"] = 2;
oj["a"]["b"]["x"] = 3;
oj["m"] = {1, 2, {{"w", 4}}};
const ordered_json copy(oj);
SECTION("the copy is equal to the original")
{
CHECK(copy == oj);
CHECK(copy.dump() == oj.dump());
}
SECTION("the key order is preserved at every level")
{
CHECK(copy.dump() == R"({"z":1,"a":{"y":2,"b":{"x":3}},"m":[1,2,{"w":4}]})");
}
SECTION("the copy is independent of the original")
{
ordered_json mutated(oj);
mutated["a"]["b"]["x"] = 99;
CHECK(oj["a"]["b"]["x"] == 3);
CHECK(mutated["a"]["b"]["x"] == 99);
}
}
TEST_CASE("regression test - diff() must account for ordered_json member order")
{
SECTION("pure reorder, no value changes")
+27
View File
@@ -658,6 +658,33 @@ TEST_CASE("regression test #5074 - portable workaround for single-element brace
CHECK(j[0] == j_obj);
}
#if defined(JSON_BRACE_INIT_COPY_SEMANTICS) && (JSON_BRACE_INIT_COPY_SEMANTICS == 1)
TEST_CASE("regression test #5074 - single-element brace init with JSON_BRACE_INIT_COPY_SEMANTICS")
{
// with JSON_BRACE_INIT_COPY_SEMANTICS: single-element brace init copies/moves
json const j_obj = {{"key", "value"}, {"num", 42}};
json const j_arr = {1, 2, 3};
// object: brace init copies instead of wrapping
json const j1{j_obj};
CHECK(j1.is_object());
CHECK(j1 == j_obj);
// array: brace init copies instead of wrapping
json const j2{j_arr};
CHECK(j2.is_array());
CHECK(j2.size() == 3);
CHECK(j2 == j_arr);
// primitives still work as initializer lists
json const j3{true};
CHECK(j3.is_boolean());
json const j4{42};
CHECK(j4.is_number_integer());
}
#endif
struct Example_5122
{
float b = 2;
+1 -1
View File
@@ -20,7 +20,7 @@ if __name__ == '__main__':
namespaces = ['nlohmann']
abi_prefix = 'json_abi'
abi_tags = ['_diag', '_ldvcmp', '_dp', '_bics']
abi_tags = ['_diag', '_ldvcmp']
version = '_v' + args.version.replace('.', '_')
inline_namespaces = []
-4
View File
@@ -60,10 +60,6 @@ int main() {
`serve_header.py` will try to read a configuration file `serve_header.yml` in the top level or project root directory, and will fall back on built-in defaults if the file cannot be read.
An annotated example configuration can be found in `tools/serve_header/serve_header.yml.example`.
By default, the server listens on `localhost` only, and only web pages from Compiler Explorer (`https://godbolt.org` and `https://compiler-explorer.com`) may read the header.
Set `bind` to serve other machines as well; anyone who can reach the server can then trigger `make` runs in your working trees.
Set `cors_origins` to allow other web pages.
## Serving `json.hpp` from multiple project directory instances or working trees
`serve_header.py` was designed with the goal of supporting multiple project roots or working trees at the same time.
+4 -20
View File
@@ -26,10 +26,6 @@ HEADER = 'json.hpp'
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
# origins whose pages may read the served header from a browser; Compiler
# Explorer downloads #include <https://...> headers client-side
DEFAULT_CORS_ORIGINS = ['https://godbolt.org', 'https://compiler-explorer.com']
JSON_VERSION_RE = re.compile(r'\s*#\s*define\s+NLOHMANN_JSON_VERSION_MAJOR\s+')
class ExitHandler(logging.StreamHandler):
@@ -251,8 +247,6 @@ class WorkTrees(FileSystemEventHandler):
self.observer.join()
class HeaderRequestHandler(SimpleHTTPRequestHandler): # lgtm[py/missing-call-to-init]
cors_origins = DEFAULT_CORS_ORIGINS
def __init__(self, request, client_address, server):
"""."""
self.worktrees = server.worktrees
@@ -316,11 +310,8 @@ class HeaderRequestHandler(SimpleHTTPRequestHandler): # lgtm[py/missing-call-to-
# set content length
super().send_header('Content-Length', length)
# CORS header; only for the configured origins
origin = self.headers.get('Origin')
if origin in self.cors_origins:
self.send_header('Access-Control-Allow-Origin', origin)
self.send_header('Vary', 'Origin')
# CORS header
self.send_header('Access-Control-Allow-Origin', '*')
# prevent caching
self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
self.send_header('Pragma', 'no-cache')
@@ -392,15 +383,8 @@ if __name__ == '__main__':
# find and monitor working trees
worktrees = WorkTrees(config.get('root', '.'))
# origins allowed to read the header from a browser
cors_origins = config.get('cors_origins', DEFAULT_CORS_ORIGINS)
if isinstance(cors_origins, str):
cors_origins = [cors_origins]
HeaderRequestHandler.cors_origins = cors_origins
# start web server; only reachable from this machine unless configured
# otherwise (bind: null listens on all interfaces)
infos = socket.getaddrinfo(config.get('bind', 'localhost'), config.get('port', 8443),
# start web server
infos = socket.getaddrinfo(config.get('bind', None), config.get('port', 8443),
type=socket.SOCK_STREAM, flags=socket.AI_PASSIVE)
DualStackServer.address_family = infos[0][0]
HeaderRequestHandler.protocol_version = 'HTTP/1.0'
+2 -9
View File
@@ -10,13 +10,6 @@
# cert_file: localhost.pem
# key_file: localhost-key.pem
# address and port for the server to listen on; by default, only this machine
# can connect. Binding to a network address, or to null for all interfaces,
# lets other machines connect, and every request runs make in a working tree.
# bind: localhost
# address and port for the server to listen on
# bind: null
# port: 8443
# origins whose web pages may read the header (CORS)
# cors_origins:
# - https://godbolt.org
# - https://compiler-explorer.com