mirror of
https://github.com/nlohmann/json.git
synced 2026-10-07 23:17:14 +00:00
Compare commits
172
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66e72cc129 | ||
|
|
cf2af0396d | ||
|
|
44fba02ec2 | ||
|
|
42f8043413 | ||
|
|
606c1e710b | ||
|
|
fbacf1f1a8 | ||
|
|
173fbc0c90 | ||
|
|
2db0e307ca | ||
|
|
923917bb7d | ||
|
|
77730529ab | ||
|
|
e0c29a1dc4 | ||
|
|
93b772553f | ||
|
|
e4f4c767c0 | ||
|
|
1d49222f4e | ||
|
|
8b04885f59 | ||
|
|
d94a8d0a5a | ||
|
|
dfa17bd446 | ||
|
|
0b6a55ea6b | ||
|
|
5286c2843b | ||
|
|
3ba495ddf9 | ||
|
|
2739c0b0af | ||
|
|
471b35c5e8 | ||
|
|
e87813232f | ||
|
|
15b0cc0561 | ||
|
|
05afd8e8e0 | ||
|
|
c908630082 | ||
|
|
c99f99ccca | ||
|
|
268ef063d3 | ||
|
|
4068a5a4d8 | ||
|
|
e0ae5040dc | ||
|
|
f579e7e055 | ||
|
|
8f185c87f7 | ||
|
|
a43bfe645b | ||
|
|
f6b6aaf69f | ||
|
|
d62e3ec9c6 | ||
|
|
3cea705804 | ||
|
|
910fbb4b5c | ||
|
|
c8ec867991 | ||
|
|
2e06ccf0c7 | ||
|
|
f17207377b | ||
|
|
3b30446d1b | ||
|
|
960d3435a1 | ||
|
|
ceb1cdb774 | ||
|
|
80761cf255 | ||
|
|
eed512f1d5 | ||
|
|
3f672f036d | ||
|
|
83302ff69d | ||
|
|
4a93680068 | ||
|
|
1fdef286dc | ||
|
|
650f3c091e | ||
|
|
4dd4505e1c | ||
|
|
d3ba92d4fb | ||
|
|
89d6a7c2b5 | ||
|
|
95c8d2aa46 | ||
|
|
d38f5f111f | ||
|
|
057c86de8c | ||
|
|
448a90909e | ||
|
|
d9e4155ba1 | ||
|
|
7130880754 | ||
|
|
89bf08760f | ||
|
|
b1595c1b40 | ||
|
|
3d6d610fdc | ||
|
|
134b2f0efe | ||
|
|
dddb2d6e43 | ||
|
|
b59fc6c902 | ||
|
|
56cf446840 | ||
|
|
babae3f35d | ||
|
|
b3ce6fcce3 | ||
|
|
cff2af5966 | ||
|
|
ad248a3290 | ||
|
|
db73471df4 | ||
|
|
c6ac5c85c2 | ||
|
|
0e57b3fb28 | ||
|
|
d575be3e55 | ||
|
|
94c518f94d | ||
|
|
76ce7e2c84 | ||
|
|
5277335a9e | ||
|
|
05b6cd0892 | ||
|
|
95d10dab70 | ||
|
|
371a8a3d9f | ||
|
|
ae01d57694 | ||
|
|
6a757ca675 | ||
|
|
cb51f80e34 | ||
|
|
9d44e3f359 | ||
|
|
9d88ead578 | ||
|
|
414d378bb4 | ||
|
|
7e459c5366 | ||
|
|
0a97d94497 | ||
|
|
bee66ec810 | ||
|
|
1df24a0bd9 | ||
|
|
a279e6f4b9 | ||
|
|
b61c04bbe4 | ||
|
|
779ae7fffc | ||
|
|
a59d1f64e9 | ||
|
|
8db804ca8f | ||
|
|
a496c709f4 | ||
|
|
57bdb2f67f | ||
|
|
b6d6d4996a | ||
|
|
dcc81f43f0 | ||
|
|
467ccfd480 | ||
|
|
41233a5d41 | ||
|
|
383ce0b040 | ||
|
|
877241a51a | ||
|
|
d04f78864c | ||
|
|
51ee239b3c | ||
|
|
6e50766dc3 | ||
|
|
d4fab0971f | ||
|
|
eede67ca92 | ||
|
|
82b31f31b8 | ||
|
|
798f4c888a | ||
|
|
98258aa3af | ||
|
|
904c8c6710 | ||
|
|
1f0c3be6f3 | ||
|
|
c3b51addbf | ||
|
|
a91f6f489f | ||
|
|
9371f2b6c7 | ||
|
|
9deb721adc | ||
|
|
ecf9df7c45 | ||
|
|
b992e1f76a | ||
|
|
a7fa8d04e8 | ||
|
|
677507137b | ||
|
|
21d90fac1c | ||
|
|
e842f3a68f | ||
|
|
bc56ac6d9a | ||
|
|
bfb2b0cb48 | ||
|
|
8cccae029b | ||
|
|
c7e534d41e | ||
|
|
83d72fd7a7 | ||
|
|
da971a52c8 | ||
|
|
4c6a64507b | ||
|
|
c62aa8ac67 | ||
|
|
a7256deba3 | ||
|
|
3a4eddf4ac | ||
|
|
43e2d9c525 | ||
|
|
0650a48659 | ||
|
|
29bb5c48b8 | ||
|
|
e17e23a2b1 | ||
|
|
c2d7177d30 | ||
|
|
08dcdaf9b8 | ||
|
|
7dae258350 | ||
|
|
7da943c5c4 | ||
|
|
2d7024eed1 | ||
|
|
d71a494367 | ||
|
|
d9a71e7bc5 | ||
|
|
20d0723b67 | ||
|
|
06bebe2af6 | ||
|
|
723cf14be4 | ||
|
|
8c1b230277 | ||
|
|
061c30310e | ||
|
|
2f7d2548e1 | ||
|
|
ad73c78923 | ||
|
|
08e677ad61 | ||
|
|
d7c6c36979 | ||
|
|
cf352d4ef5 | ||
|
|
e96e2982a5 | ||
|
|
0a64e6c99b | ||
|
|
d8b8c2498f | ||
|
|
2fb66ba859 | ||
|
|
ab49a7b1bf | ||
|
|
154f240022 | ||
|
|
8d3280de1e | ||
|
|
118af5015e | ||
|
|
9b0b4ff26d | ||
|
|
694bfd1b4e | ||
|
|
ad82821c89 | ||
|
|
1bf0b1b6c2 | ||
|
|
b88e5f9107 | ||
|
|
d23803fd32 | ||
|
|
f1014c938a | ||
|
|
e91fdad877 | ||
|
|
9c71689715 | ||
|
|
44ec53c77b |
No files matched your search
@@ -205,9 +205,6 @@ API of the 3.x.y version is broken. This includes:
|
|||||||
- Changing access specifiers.
|
- Changing access specifiers.
|
||||||
- Changing default arguments.
|
- Changing default arguments.
|
||||||
|
|
||||||
What is and is not covered by this guarantee is described in the
|
|
||||||
[roadmap](https://json.nlohmann.me/community/roadmap/#api-stability).
|
|
||||||
|
|
||||||
Although these guidelines may seem restrictive, they are essential for maintaining the library’s utility.
|
Although these guidelines may seem restrictive, they are essential for maintaining the library’s utility.
|
||||||
|
|
||||||
Breaking changes may be introduced when they are guarded with a feature macro such as
|
Breaking changes may be introduced when they are guarded with a feature macro such as
|
||||||
|
|||||||
@@ -16,9 +16,6 @@ only_commits:
|
|||||||
|
|
||||||
environment:
|
environment:
|
||||||
matrix:
|
matrix:
|
||||||
# The Visual Studio 2017 jobs compile everything with /std:c++17, so they
|
|
||||||
# only build the C++17 variant of each test, split into two jobs each to
|
|
||||||
# stay below AppVeyor's 60-minute limit per job.
|
|
||||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||||
configuration: Debug
|
configuration: Debug
|
||||||
platform: x86
|
platform: x86
|
||||||
@@ -37,13 +34,7 @@ environment:
|
|||||||
configuration: Release
|
configuration: Release
|
||||||
platform: x86
|
platform: x86
|
||||||
CXX_FLAGS: "/permissive- /std:c++17 /utf-8 /W4 /WX"
|
CXX_FLAGS: "/permissive- /std:c++17 /utf-8 /W4 /WX"
|
||||||
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=0/2"
|
CMAKE_OPTIONS: ""
|
||||||
GENERATOR: Visual Studio 15 2017
|
|
||||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
|
||||||
configuration: Release
|
|
||||||
platform: x86
|
|
||||||
CXX_FLAGS: "/permissive- /std:c++17 /utf-8 /W4 /WX"
|
|
||||||
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=1/2"
|
|
||||||
GENERATOR: Visual Studio 15 2017
|
GENERATOR: Visual Studio 15 2017
|
||||||
|
|
||||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||||
@@ -64,13 +55,7 @@ environment:
|
|||||||
configuration: Release
|
configuration: Release
|
||||||
platform: x64
|
platform: x64
|
||||||
CXX_FLAGS: "/permissive- /std:c++17 /Zc:__cplusplus /utf-8 /W4 /WX"
|
CXX_FLAGS: "/permissive- /std:c++17 /Zc:__cplusplus /utf-8 /W4 /WX"
|
||||||
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=0/2"
|
CMAKE_OPTIONS: ""
|
||||||
GENERATOR: Visual Studio 15 2017
|
|
||||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
|
||||||
configuration: Release
|
|
||||||
platform: x64
|
|
||||||
CXX_FLAGS: "/permissive- /std:c++17 /Zc:__cplusplus /utf-8 /W4 /WX"
|
|
||||||
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=1/2"
|
|
||||||
GENERATOR: Visual Studio 15 2017
|
GENERATOR: Visual Studio 15 2017
|
||||||
|
|
||||||
init:
|
init:
|
||||||
@@ -81,10 +66,15 @@ install:
|
|||||||
- if "%platform%"=="x86" set GENERATOR_PLATFORM=Win32
|
- if "%platform%"=="x86" set GENERATOR_PLATFORM=Win32
|
||||||
|
|
||||||
before_build:
|
before_build:
|
||||||
- cmake . -G "%GENERATOR%" -A "%GENERATOR_PLATFORM%" -DCMAKE_CXX_FLAGS="%CXX_FLAGS%" -DCMAKE_IGNORE_PATH="C:/Program Files/Git/usr/bin" -DJSON_BuildTests=On %CMAKE_OPTIONS%
|
- cmake . -G "%GENERATOR%" -A "%GENERATOR_PLATFORM%" -DCMAKE_CXX_FLAGS="%CXX_FLAGS%" -DCMAKE_IGNORE_PATH="C:/Program Files/Git/usr/bin" -DJSON_BuildTests=On "%CMAKE_OPTIONS%"
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- cmake --build . --config "%configuration%" --parallel 2
|
- cmake --build . --config "%configuration%" --parallel 2
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- ctest -C "%configuration%" --parallel 2 --output-on-failure
|
- if "%configuration%"=="Release" ctest -C "%configuration%" --parallel 2 --output-on-failure
|
||||||
|
# On Debug builds, skip test-unicode_all
|
||||||
|
# as it is extremely slow to run and cause
|
||||||
|
# occasional timeouts on AppVeyor.
|
||||||
|
# More info: https://github.com/nlohmann/json/pull/1570
|
||||||
|
- if "%configuration%"=="Debug" ctest --exclude-regex "test-unicode" -C "%configuration%" --parallel 2 --output-on-failure
|
||||||
@@ -52,6 +52,7 @@ labels:
|
|||||||
- "single_include/nlohmann/json_view\\.hpp"
|
- "single_include/nlohmann/json_view\\.hpp"
|
||||||
- "tests/src/unit-json_view.*"
|
- "tests/src/unit-json_view.*"
|
||||||
- "tests/src/fuzzer-(parse_json_view|json_view_image)\\.cpp"
|
- "tests/src/fuzzer-(parse_json_view|json_view_image)\\.cpp"
|
||||||
|
- "tests/benchmarks/json_view/.*"
|
||||||
- "tools/amalgamate/config_json_view\\.json"
|
- "tools/amalgamate/config_json_view\\.json"
|
||||||
- "docs/mkdocs/docs/features/json_view\\.md"
|
- "docs/mkdocs/docs/features/json_view\\.md"
|
||||||
- "docs/mkdocs/docs/api/basic_json_(document|view)/.*"
|
- "docs/mkdocs/docs/api/basic_json_(document|view)/.*"
|
||||||
|
|||||||
@@ -152,13 +152,6 @@ jobs:
|
|||||||
VERSION_MAJOR=$(grep -m1 'define NLOHMANN_JSON_VERSION_MAJOR' $ABI_MACROS | grep -o '[0-9]\+')
|
VERSION_MAJOR=$(grep -m1 'define NLOHMANN_JSON_VERSION_MAJOR' $ABI_MACROS | grep -o '[0-9]\+')
|
||||||
VERSION_MINOR=$(grep -m1 'define NLOHMANN_JSON_VERSION_MINOR' $ABI_MACROS | grep -o '[0-9]\+')
|
VERSION_MINOR=$(grep -m1 'define NLOHMANN_JSON_VERSION_MINOR' $ABI_MACROS | grep -o '[0-9]\+')
|
||||||
VERSION_PATCH=$(grep -m1 'define NLOHMANN_JSON_VERSION_PATCH' $ABI_MACROS | grep -o '[0-9]\+')
|
VERSION_PATCH=$(grep -m1 'define NLOHMANN_JSON_VERSION_PATCH' $ABI_MACROS | grep -o '[0-9]\+')
|
||||||
# The template is what this check verifies the committed natvis
|
|
||||||
# file against, so take it from this PR rather than from the
|
|
||||||
# develop checkout: generate_natvis.py loads the template from its
|
|
||||||
# own directory, and with develop's copy a PR that changes the
|
|
||||||
# template could never pass. Like the macro_builder step above,
|
|
||||||
# this only renders PR content in a job without credentials.
|
|
||||||
cp tools/generate_natvis/nlohmann_json.natvis.j2 $NATVIS_TOOL_DIR/
|
|
||||||
python3 $NATVIS_TOOL_DIR/generate_natvis.py --version "$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH" $MAIN_DIR
|
python3 $NATVIS_TOOL_DIR/generate_natvis.py --version "$VERSION_MAJOR.$VERSION_MINOR.$VERSION_PATCH" $MAIN_DIR
|
||||||
|
|
||||||
- name: Build patch and check for differences
|
- name: Build patch and check for differences
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
name: "json_view benchmarks"
|
||||||
|
|
||||||
|
# On demand only: runs the comparison of json_view with yyjson, simdjson, and
|
||||||
|
# Boost.JSON (tests/benchmarks/json_view/compare.py) on GitHub-hosted runners,
|
||||||
|
# for numbers from x86-64 and AArch64 Linux. It runs when started by hand, or
|
||||||
|
# when a pull request gets the label "benchmark" (on both architectures, with
|
||||||
|
# GCC and the default settings). Shared runners are noisy: the results show
|
||||||
|
# where json_view stands, but published numbers need a quiet machine (see
|
||||||
|
# tests/benchmarks/json_view/README.md).
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [labeled]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
runner:
|
||||||
|
description: "Runner image"
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- ubuntu-24.04
|
||||||
|
- ubuntu-24.04-arm
|
||||||
|
default: ubuntu-24.04
|
||||||
|
compiler:
|
||||||
|
description: "Compiler"
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- g++
|
||||||
|
- clang++
|
||||||
|
default: g++
|
||||||
|
native:
|
||||||
|
description: "Compile for the runner's CPU (-march=native)"
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
rounds:
|
||||||
|
description: "Rounds of bench_view"
|
||||||
|
type: number
|
||||||
|
default: 30
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
compare:
|
||||||
|
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'benchmark'
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
runner: ${{ fromJSON(github.event_name == 'workflow_dispatch' && format('["{0}"]', inputs.runner) || '["ubuntu-24.04", "ubuntu-24.04-arm"]') }}
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Download test data
|
||||||
|
run: |
|
||||||
|
cmake -S . -B build -DJSON_BuildTests=On
|
||||||
|
cmake --build build --target download_test_data
|
||||||
|
|
||||||
|
- name: Run the comparison
|
||||||
|
env:
|
||||||
|
CXX: ${{ inputs.compiler || 'g++' }}
|
||||||
|
CC: ${{ inputs.compiler == 'clang++' && 'clang' || 'gcc' }}
|
||||||
|
ROUNDS: ${{ inputs.rounds || 30 }}
|
||||||
|
NATIVE: ${{ inputs.native && '--native' || '' }}
|
||||||
|
run: python3 tests/benchmarks/json_view/compare.py --data build/test_files --download --rounds "$ROUNDS" $NATIVE
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
run: cat tests/benchmarks/json_view/results/*.md >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
with:
|
||||||
|
name: json_view-benchmarks-${{ matrix.runner }}-${{ inputs.compiler || 'g++' }}
|
||||||
|
path: tests/benchmarks/json_view/results/
|
||||||
@@ -17,11 +17,11 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
macos-15:
|
macos-14:
|
||||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
runs-on: macos-14 # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1', '26.1.1', '26.2', '26.3']
|
xcode: ['15.0.1', '15.1', '15.2', '15.3', '15.4']
|
||||||
env:
|
env:
|
||||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
@@ -36,11 +36,11 @@ jobs:
|
|||||||
- name: Test
|
- name: Test
|
||||||
run: cd build ; ctest -j 10 --output-on-failure
|
run: cd build ; ctest -j 10 --output-on-failure
|
||||||
|
|
||||||
macos-26:
|
macos-15:
|
||||||
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
|
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
xcode: ['26.4.1', '26.5', '26.6']
|
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1']
|
||||||
env:
|
env:
|
||||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
@@ -71,38 +71,3 @@ jobs:
|
|||||||
run: cmake --build build --parallel 10
|
run: cmake --build build --parallel 10
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cd build ; ctest -j 10 --output-on-failure
|
run: cd build ; ctest -j 10 --output-on-failure
|
||||||
|
|
||||||
swiftpm:
|
|
||||||
runs-on: macos-15
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- name: Check that Package.swift resolves without a deprecation warning
|
|
||||||
run: swift package dump-package
|
|
||||||
- name: Build the SwiftPM documentation example against this checkout
|
|
||||||
run: |
|
|
||||||
mkdir -p /tmp/json-swiftpm-consumer/Sources/MyLibrary
|
|
||||||
cp docs/mkdocs/docs/integration/swift/example.cpp /tmp/json-swiftpm-consumer/Sources/MyLibrary/example.cpp
|
|
||||||
cat > /tmp/json-swiftpm-consumer/Package.swift << EOF
|
|
||||||
// swift-tools-version: 5.9
|
|
||||||
import PackageDescription
|
|
||||||
|
|
||||||
let package = Package(
|
|
||||||
name: "MyPackage",
|
|
||||||
dependencies: [
|
|
||||||
.package(path: "${{ github.workspace }}")
|
|
||||||
],
|
|
||||||
targets: [
|
|
||||||
.target(
|
|
||||||
name: "MyLibrary",
|
|
||||||
dependencies: [
|
|
||||||
.product(name: "json", package: "json")
|
|
||||||
],
|
|
||||||
publicHeadersPath: "."
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
EOF
|
|
||||||
cd /tmp/json-swiftpm-consumer
|
|
||||||
swift build
|
|
||||||
@@ -107,7 +107,7 @@ jobs:
|
|||||||
container: ubuntu:24.04
|
container: ubuntu:24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_delete_deprecated_functions, 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_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local]
|
||||||
steps:
|
steps:
|
||||||
- name: Install build-essential
|
- name: Install build-essential
|
||||||
run: apt-get update ; apt-get install -y build-essential unzip wget git
|
run: apt-get update ; apt-get install -y build-essential unzip wget git
|
||||||
@@ -209,7 +209,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# older GCC docker images (4, 5, 6) fail to check out code
|
# older GCC docker images (4, 5, 6) fail to check out code
|
||||||
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', '16', 'latest']
|
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', 'latest']
|
||||||
container: gcc:${{ matrix.compiler }}
|
container: gcc:${{ matrix.compiler }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --parallel 10
|
run: cmake --build build --parallel 10
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cd build ; ctest -j 10 -C Debug --output-on-failure
|
run: cd build ; ctest -j 10 -C Debug --exclude-regex "test-unicode" --output-on-failure
|
||||||
|
|
||||||
clang-cl-12:
|
clang-cl-12:
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
@@ -195,7 +195,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: cmake --build build --config Debug --parallel 10
|
run: cmake --build build --config Debug --parallel 10
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cd build ; ctest -j 10 -C Debug --output-on-failure
|
run: cd build ; ctest -j 10 -C Debug --exclude-regex "test-unicode" --output-on-failure
|
||||||
|
|
||||||
ci_module_cpp20:
|
ci_module_cpp20:
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
{
|
{
|
||||||
"_comment": "Used by the ci_infer CMake target (#5715 item 4b). fail-on-issue makes CI fail on Infer findings; disable-issue-type is a type-level baseline for the ~174 pre-existing findings (all PULSE_UNNECESSARY_COPY*/PULSE_RESOURCE_LEAK/PULSE_CONST_REFABLE, mostly in test code) triaged in run https://github.com/nlohmann/json/actions/runs/35829411620 on commit 1054b2097, so CI fails only on a NEW issue type. Remove an entry here once its findings have been fixed or explicitly accepted.",
|
"_comment": "Used by the ci_infer CMake target (#5715 item 4b). fail-on-issue makes CI fail on Infer findings; disable-issue-type is a type-level baseline for the ~174 pre-existing findings (all PULSE_UNNECESSARY_COPY*/PULSE_RESOURCE_LEAK/PULSE_CONST_REFABLE, mostly in test code) triaged in run https://github.com/nlohmann/json/actions/runs/35829411620 on commit 1054b2097, so CI fails only on a NEW issue type. Remove an entry here once its findings have been fixed or explicitly accepted.",
|
||||||
"_comment_pulse": "Pulse stops exploring paths after pulse-max-disjuncts (default 20). With the default, basic_json::replace_value() (destroy + assert_invariant) exceeds the limit, Pulse loses the stored type, and reports false NULLPTR_DEREFERENCE findings for get_ptr() results in tests/src/unit-pointer_access.cpp.",
|
|
||||||
"pulse-max-disjuncts": 40,
|
|
||||||
"fail-on-issue": true,
|
"fail-on-issue": true,
|
||||||
"disable-issue-type": [
|
"disable-issue-type": [
|
||||||
"PULSE_UNNECESSARY_COPY_ASSIGNMENT",
|
"PULSE_UNNECESSARY_COPY_ASSIGNMENT",
|
||||||
|
|||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: json
|
||||||
|
Upstream-Contact: Niels Lohmann <mail@nlohmann.me>
|
||||||
|
Source: https://github.com/nlohmann/json
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
License: MIT
|
||||||
|
|
||||||
|
Files: include/nlohmann/thirdparty/hedley.hpp
|
||||||
|
Copyright: 2016-2021 Evan Nemerson <evan@nemerson.com>
|
||||||
|
License: CC0
|
||||||
|
|
||||||
|
Files: include/nlohmann/detail/meta/cpp_future.hpp
|
||||||
|
Copyright: 2013-2026 Niels Lohmann <https://nlohmann.me> and 2018 The Abseil Authors
|
||||||
|
License: MIT AND Apache-2.0
|
||||||
|
|
||||||
|
Files: tests/thirdparty/doctest/*
|
||||||
|
Copyright: 2016-2023 Viktor Kirilov
|
||||||
|
License: MIT
|
||||||
|
|
||||||
|
Files: tests/thirdparty/fifo_map/*
|
||||||
|
Copyright: 2015-2017 Niels Lohmann
|
||||||
|
License: MIT
|
||||||
|
|
||||||
|
Files: tests/thirdparty/imapdl/*
|
||||||
|
Copyright: 2017 Georg Sauthoff <mail@gms.tf>
|
||||||
|
License: GPL-3.0-only
|
||||||
|
|
||||||
|
Files: tools/amalgamate/*
|
||||||
|
Copyright: 2012 Erik Edlund <erik.edlund@32767.se>
|
||||||
|
License: BSD-3-Clause
|
||||||
|
|
||||||
|
Files: tools/gdb_pretty_printer/*
|
||||||
|
Copyright: 2020 Hannes Domani <https://github.com/ssbssa>
|
||||||
|
License: MIT
|
||||||
@@ -105,7 +105,6 @@ cc_library(
|
|||||||
name = "singleheader-json",
|
name = "singleheader-json",
|
||||||
hdrs = [
|
hdrs = [
|
||||||
"single_include/nlohmann/json.hpp",
|
"single_include/nlohmann/json.hpp",
|
||||||
"single_include/nlohmann/json_fwd.hpp",
|
|
||||||
"single_include/nlohmann/json_view.hpp",
|
"single_include/nlohmann/json_view.hpp",
|
||||||
],
|
],
|
||||||
includes = ["single_include"],
|
includes = ["single_include"],
|
||||||
|
|||||||
+1
-1
@@ -10,5 +10,5 @@ title: "JSON for Modern C++"
|
|||||||
version: 3.12.0
|
version: 3.12.0
|
||||||
date-released: 2025-04-07
|
date-released: 2025-04-07
|
||||||
license: MIT
|
license: MIT
|
||||||
repository-code: "https://github.com/nlohmann/json"
|
repository-code: "https://github.com/nlohmann"
|
||||||
url: https://json.nlohmann.me
|
url: https://json.nlohmann.me
|
||||||
+1
-10
@@ -42,11 +42,8 @@ endif()
|
|||||||
## OPTIONS
|
## OPTIONS
|
||||||
##
|
##
|
||||||
|
|
||||||
# Build the tests by default only for the main project and only if the tests
|
|
||||||
# directory exists (the release archive json.tar.xz does not contain it).
|
|
||||||
# VERSION_GREATER_EQUAL is not available in older CMake (< 3.7)
|
# VERSION_GREATER_EQUAL is not available in older CMake (< 3.7)
|
||||||
if(${MAIN_PROJECT} AND (${CMAKE_VERSION} VERSION_EQUAL 3.13 OR ${CMAKE_VERSION} VERSION_GREATER 3.13)
|
if(${MAIN_PROJECT} AND (${CMAKE_VERSION} VERSION_EQUAL 3.13 OR ${CMAKE_VERSION} VERSION_GREATER 3.13))
|
||||||
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/CMakeLists.txt")
|
|
||||||
set(JSON_BuildTests_INIT ON)
|
set(JSON_BuildTests_INIT ON)
|
||||||
else()
|
else()
|
||||||
set(JSON_BuildTests_INIT OFF)
|
set(JSON_BuildTests_INIT OFF)
|
||||||
@@ -65,7 +62,6 @@ option(JSON_MultipleHeaders "Use non-amalgamated version of the l
|
|||||||
option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF)
|
option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF)
|
||||||
option(JSON_StrictNulHandling "Build with strict NUL-byte handling enabled." OFF)
|
option(JSON_StrictNulHandling "Build with strict NUL-byte handling enabled." OFF)
|
||||||
option(JSON_StrictBinaryUTF8 "Build with UTF-8 checks in the CBOR, UBJSON, BJData, and BSON writers enabled." OFF)
|
option(JSON_StrictBinaryUTF8 "Build with UTF-8 checks in the CBOR, UBJSON, BJData, and BSON writers enabled." OFF)
|
||||||
option(JSON_DeleteDeprecatedFunctions "Delete the deprecated functions instead of only deprecating them." OFF)
|
|
||||||
|
|
||||||
if (JSON_CI)
|
if (JSON_CI)
|
||||||
include(ci)
|
include(ci)
|
||||||
@@ -127,10 +123,6 @@ if (JSON_StrictBinaryUTF8)
|
|||||||
message(STATUS "Strict UTF-8 checks in binary writers enabled (JSON_STRICT_BINARY_UTF8=1)")
|
message(STATUS "Strict UTF-8 checks in binary writers enabled (JSON_STRICT_BINARY_UTF8=1)")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (JSON_DeleteDeprecatedFunctions)
|
|
||||||
message(STATUS "Deprecated functions are deleted (JSON_DELETE_DEPRECATED_FUNCTIONS=1)")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (JSON_Diagnostic_Positions)
|
if (JSON_Diagnostic_Positions)
|
||||||
message(STATUS "Diagnostic positions enabled (JSON_DIAGNOSTIC_POSITIONS=1)")
|
message(STATUS "Diagnostic positions enabled (JSON_DIAGNOSTIC_POSITIONS=1)")
|
||||||
endif()
|
endif()
|
||||||
@@ -167,7 +159,6 @@ target_compile_definitions(
|
|||||||
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
|
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
|
||||||
$<$<BOOL:${JSON_StrictNulHandling}>:JSON_STRICT_NUL_HANDLING=1>
|
$<$<BOOL:${JSON_StrictNulHandling}>:JSON_STRICT_NUL_HANDLING=1>
|
||||||
$<$<BOOL:${JSON_StrictBinaryUTF8}>:JSON_STRICT_BINARY_UTF8=1>
|
$<$<BOOL:${JSON_StrictBinaryUTF8}>:JSON_STRICT_BINARY_UTF8=1>
|
||||||
$<$<BOOL:${JSON_DeleteDeprecatedFunctions}>:JSON_DELETE_DEPRECATED_FUNCTIONS=1>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
|
|||||||
@@ -196,19 +196,19 @@ Further documentation:
|
|||||||
|
|
||||||
## REUSE
|
## REUSE
|
||||||
|
|
||||||
### `REUSE.toml`
|
### `.reuse/dep5`
|
||||||
|
|
||||||
The file defines the licenses of certain third-party components in the repository. The root `Makefile` contains a target `reuse` that checks for compliance.
|
The file defines the licenses of certain third-party components in the repository. The root `Makefile` contains a target `reuse` that checks for compliance.
|
||||||
|
|
||||||
Further documentation:
|
Further documentation:
|
||||||
|
|
||||||
- [REUSE.toml](https://reuse.software/spec-3.3/#reusetoml)
|
- [DEP5](https://reuse.software/spec-3.2/#dep5-deprecated)
|
||||||
- [reuse command-line tool](https://pypi.org/project/reuse/)
|
- [reuse command-line tool](https://pypi.org/project/reuse/)
|
||||||
- [documentation of linting](https://reuse.readthedocs.io/en/stable/man/reuse-lint.html)
|
- [documentation of linting](https://reuse.readthedocs.io/en/stable/man/reuse-lint.html)
|
||||||
- [REUSE](http://reuse.software)
|
- [REUSE](http://reuse.software)
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> The filename `REUSE.toml` is predetermined by REUSE. Alternatively, a `.reuse/dep5` file (deprecated) can be used.
|
> The filename `.reuse/dep5` is predetermined by REUSE. Alternatively, a `REUSE.toml` file can be used.
|
||||||
|
|
||||||
### `.reuse/templates`
|
### `.reuse/templates`
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,6 @@
|
|||||||
# find GNU sed to use `-i` parameter
|
# find GNU sed to use `-i` parameter
|
||||||
SED:=$(shell command -v gsed || which sed)
|
SED:=$(shell command -v gsed || which sed)
|
||||||
|
|
||||||
# find GNU tar to use `--sort` and `--pax-option` parameters
|
|
||||||
TAR:=$(shell command -v gtar || which tar)
|
|
||||||
|
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# source files
|
# source files
|
||||||
@@ -184,8 +181,8 @@ ChangeLog.md:
|
|||||||
# archive is created according to the advices of <https://reproducible-builds.org/docs/archives/>.
|
# archive is created according to the advices of <https://reproducible-builds.org/docs/archives/>.
|
||||||
json.tar.xz:
|
json.tar.xz:
|
||||||
mkdir json
|
mkdir json
|
||||||
rsync -R $(shell find LICENSE.MIT nlohmann_json.natvis CMakeLists.txt cmake/*.in include single_include src/modules -type f) json
|
rsync -R $(shell find LICENSE.MIT nlohmann_json.natvis CMakeLists.txt cmake/*.in include single_include -type f) json
|
||||||
$(TAR) --sort=name --mtime="@$(shell git log -1 --pretty=%ct)" --owner=0 --group=0 --numeric-owner --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime --create --file - json | xz --compress -9e --threads=2 - > json.tar.xz
|
gtar --sort=name --mtime="@$(shell git log -1 --pretty=%ct)" --owner=0 --group=0 --numeric-owner --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime --create --file - json | xz --compress -9e --threads=2 - > json.tar.xz
|
||||||
rm -fr json
|
rm -fr json
|
||||||
|
|
||||||
# We use `-X` to make the resulting ZIP file reproducible, see
|
# We use `-X` to make the resulting ZIP file reproducible, see
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ import PackageDescription
|
|||||||
let package = Package(
|
let package = Package(
|
||||||
name: "nlohmann-json",
|
name: "nlohmann-json",
|
||||||
platforms: [
|
platforms: [
|
||||||
.iOS(.v12), .macOS(.v10_13), .tvOS(.v12), .watchOS(.v9), .visionOS(.v1)
|
.iOS(.v12), .macOS(.v10_13), .tvOS(.v12), .watchOS(.v4), .visionOS(.v1)
|
||||||
],
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(name: "json", targets: ["json"])
|
.library(name: "json", targets: ["json"])
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
[](https://coveralls.io/github/nlohmann/json?branch=develop)
|
[](https://coveralls.io/github/nlohmann/json?branch=develop)
|
||||||
[](https://scan.coverity.com/projects/nlohmann-json)
|
[](https://scan.coverity.com/projects/nlohmann-json)
|
||||||
[](https://app.codacy.com/gh/nlohmann/json/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
[](https://app.codacy.com/gh/nlohmann/json/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
||||||
[](https://issues.oss-fuzz.com/issues?q=project:json)
|
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:json)
|
||||||
[](https://wandbox.org/permlink/1mp10JbaANo6FUc7)
|
[](https://wandbox.org/permlink/1mp10JbaANo6FUc7)
|
||||||
[](https://json.nlohmann.me)
|
[](https://json.nlohmann.me)
|
||||||
[](https://raw.githubusercontent.com/nlohmann/json/develop/LICENSE.MIT)
|
[](https://raw.githubusercontent.com/nlohmann/json/develop/LICENSE.MIT)
|
||||||
@@ -361,7 +361,7 @@ std::cout << j_string << " == " << serialized_string << std::endl;
|
|||||||
|
|
||||||
[`.dump()`](https://json.nlohmann.me/api/basic_json/dump/) returns the originally stored string value.
|
[`.dump()`](https://json.nlohmann.me/api/basic_json/dump/) returns the originally stored string value.
|
||||||
|
|
||||||
Note the library only supports UTF-8. When you store strings with different encodings in the library, calling [`dump()`](https://json.nlohmann.me/api/basic_json/dump/) may throw an exception unless `json::error_handler_t::replace`, `json::error_handler_t::ignore`, or `json::error_handler_t::keep` are used as error handlers.
|
Note the library only supports UTF-8. When you store strings with different encodings in the library, calling [`dump()`](https://json.nlohmann.me/api/basic_json/dump/) may throw an exception unless `json::error_handler_t::replace` or `json::error_handler_t::ignore` are used as error handlers.
|
||||||
|
|
||||||
#### To/from streams (e.g., files, string streams)
|
#### To/from streams (e.g., files, string streams)
|
||||||
|
|
||||||
@@ -1210,18 +1210,15 @@ language bindings, format converters, and the like. See the curated [Ecosystem](
|
|||||||
|
|
||||||
Though it's 2026 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:
|
Though it's 2026 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:
|
||||||
|
|
||||||
- GCC 4.8 - 16.2 (and possibly later)
|
- GCC 4.8 - 14.2 (and possibly later)
|
||||||
- Clang 3.4 - 22.1 (and possibly later)
|
- Clang 3.4 - 21.0 (and possibly later)
|
||||||
- Apple Clang 15.0 - 21.0 (and possibly later)
|
- Apple Clang 9.1 - 16.0 (and possibly later)
|
||||||
- Intel C++ Compiler Classic (icpc) 2021.10
|
- Intel C++ Compiler 17.0.2 (and possibly later)
|
||||||
- Intel oneAPI DPC++/C++ Compiler (icpx) 2025.3 (and possibly later)
|
- Nvidia CUDA Compiler 11.0.221 (and possibly later)
|
||||||
- NVIDIA CUDA Compiler (nvcc) 11.8 - 12.6 (and possibly later)
|
- Microsoft Visual C++ 2015 / Build Tools 14.0.25123.0 (and possibly later)
|
||||||
- NVIDIA HPC SDK C++ Compiler (nvc++) 25.5 (and possibly later)
|
- Microsoft Visual C++ 2017 / Build Tools 15.5.180.51428 (and possibly later)
|
||||||
- Microsoft Visual C++ 2015 / MSVC 19.0 (and possibly later)
|
- Microsoft Visual C++ 2019 / Build Tools 16.3.1+1def00d3d (and possibly later)
|
||||||
- Microsoft Visual C++ 2017 / MSVC 19.16 (and possibly later)
|
- Microsoft Visual C++ 2022 / Build Tools 19.30.30709.0 (and possibly later)
|
||||||
- Microsoft Visual C++ 2019 / MSVC 19.29 (and possibly later)
|
|
||||||
- Microsoft Visual C++ 2022 / MSVC 19.44 (and possibly later)
|
|
||||||
- Microsoft Visual C++ 2026 / MSVC 19.51 (and possibly later)
|
|
||||||
|
|
||||||
I would be happy to learn about other compilers/versions.
|
I would be happy to learn about other compilers/versions.
|
||||||
|
|
||||||
@@ -1415,7 +1412,7 @@ The library is compliant to version 3.3 of the [**REUSE specification**](https:/
|
|||||||
|
|
||||||
- Every source file contains an SPDX copyright header.
|
- Every source file contains an SPDX copyright header.
|
||||||
- The full text of all licenses used in the repository can be found in the `LICENSES` folder.
|
- The full text of all licenses used in the repository can be found in the `LICENSES` folder.
|
||||||
- File `REUSE.toml` contains an overview of all files' copyrights and licenses.
|
- File `.reuse/dep5` contains an overview of all files' copyrights and licenses.
|
||||||
- Run `pipx run reuse lint` to verify the project's REUSE compliance and `pipx run reuse spdx` to generate a SPDX SBOM.
|
- Run `pipx run reuse lint` to verify the project's REUSE compliance and `pipx run reuse spdx` to generate a SPDX SBOM.
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
@@ -1928,7 +1925,7 @@ The library supports **Unicode input** as follows:
|
|||||||
- [Unicode noncharacters](https://www.unicode.org/faq/private_use.html#nonchar1) will not be replaced by the library.
|
- [Unicode noncharacters](https://www.unicode.org/faq/private_use.html#nonchar1) will not be replaced by the library.
|
||||||
- Invalid surrogates (e.g., incomplete pairs such as `\uDEAD`) will yield parse errors.
|
- Invalid surrogates (e.g., incomplete pairs such as `\uDEAD`) will yield parse errors.
|
||||||
- The strings stored in the library are UTF-8 encoded. When using the default string type (`std::string`), note that its length/size functions return the number of stored bytes rather than the number of characters or glyphs.
|
- The strings stored in the library are UTF-8 encoded. When using the default string type (`std::string`), note that its length/size functions return the number of stored bytes rather than the number of characters or glyphs.
|
||||||
- When you store strings with different encodings in the library, calling [`dump()`](https://json.nlohmann.me/api/basic_json/dump/) may throw an exception unless `json::error_handler_t::replace`, `json::error_handler_t::ignore`, or `json::error_handler_t::keep` are used as error handlers.
|
- When you store strings with different encodings in the library, calling [`dump()`](https://json.nlohmann.me/api/basic_json/dump/) may throw an exception unless `json::error_handler_t::replace` or `json::error_handler_t::ignore` are used as error handlers.
|
||||||
- To store wide strings (e.g., `std::wstring`), you need to convert them to a UTF-8 encoded `std::string` before, see [an example](https://json.nlohmann.me/home/faq/#wide-string-handling).
|
- To store wide strings (e.g., `std::wstring`), you need to convert them to a UTF-8 encoded `std::string` before, see [an example](https://json.nlohmann.me/home/faq/#wide-string-handling).
|
||||||
|
|
||||||
### Comments in JSON
|
### Comments in JSON
|
||||||
|
|||||||
-52
@@ -1,52 +0,0 @@
|
|||||||
version = 1
|
|
||||||
SPDX-PackageName = "json"
|
|
||||||
SPDX-PackageSupplier = "Niels Lohmann <mail@nlohmann.me>"
|
|
||||||
SPDX-PackageDownloadLocation = "https://github.com/nlohmann/json"
|
|
||||||
|
|
||||||
[[annotations]]
|
|
||||||
path = "**"
|
|
||||||
precedence = "aggregate"
|
|
||||||
SPDX-FileCopyrightText = "2013-2026 Niels Lohmann <https://nlohmann.me>"
|
|
||||||
SPDX-License-Identifier = "MIT"
|
|
||||||
|
|
||||||
[[annotations]]
|
|
||||||
path = "include/nlohmann/thirdparty/hedley.hpp"
|
|
||||||
precedence = "aggregate"
|
|
||||||
SPDX-FileCopyrightText = "2016-2021 Evan Nemerson <evan@nemerson.com>"
|
|
||||||
SPDX-License-Identifier = "CC0"
|
|
||||||
|
|
||||||
[[annotations]]
|
|
||||||
path = "include/nlohmann/detail/meta/cpp_future.hpp"
|
|
||||||
precedence = "aggregate"
|
|
||||||
SPDX-FileCopyrightText = "2013-2026 Niels Lohmann <https://nlohmann.me> and 2018 The Abseil Authors"
|
|
||||||
SPDX-License-Identifier = "MIT AND Apache-2.0"
|
|
||||||
|
|
||||||
[[annotations]]
|
|
||||||
path = "tests/thirdparty/doctest/**"
|
|
||||||
precedence = "aggregate"
|
|
||||||
SPDX-FileCopyrightText = "2016-2023 Viktor Kirilov"
|
|
||||||
SPDX-License-Identifier = "MIT"
|
|
||||||
|
|
||||||
[[annotations]]
|
|
||||||
path = "tests/thirdparty/fifo_map/**"
|
|
||||||
precedence = "aggregate"
|
|
||||||
SPDX-FileCopyrightText = "2015-2017 Niels Lohmann"
|
|
||||||
SPDX-License-Identifier = "MIT"
|
|
||||||
|
|
||||||
[[annotations]]
|
|
||||||
path = "tests/thirdparty/imapdl/**"
|
|
||||||
precedence = "aggregate"
|
|
||||||
SPDX-FileCopyrightText = "2017 Georg Sauthoff <mail@gms.tf>"
|
|
||||||
SPDX-License-Identifier = "GPL-3.0-only"
|
|
||||||
|
|
||||||
[[annotations]]
|
|
||||||
path = "tools/amalgamate/**"
|
|
||||||
precedence = "aggregate"
|
|
||||||
SPDX-FileCopyrightText = "2012 Erik Edlund <erik.edlund@32767.se>"
|
|
||||||
SPDX-License-Identifier = "BSD-3-Clause"
|
|
||||||
|
|
||||||
[[annotations]]
|
|
||||||
path = "tools/gdb_pretty_printer/**"
|
|
||||||
precedence = "aggregate"
|
|
||||||
SPDX-FileCopyrightText = "2020 Hannes Domani <https://github.com/ssbssa>"
|
|
||||||
SPDX-License-Identifier = "MIT"
|
|
||||||
+8
-23
@@ -249,20 +249,6 @@ add_custom_target(ci_test_strict_nul_handling
|
|||||||
COMMENT "Compile and test with strict NUL-byte handling enabled"
|
COMMENT "Compile and test with strict NUL-byte handling enabled"
|
||||||
)
|
)
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# Delete the deprecated functions.
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
add_custom_target(ci_test_delete_deprecated_functions
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
|
||||||
-DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_DeleteDeprecatedFunctions=ON
|
|
||||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_delete_deprecated_functions
|
|
||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_delete_deprecated_functions
|
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_delete_deprecated_functions && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
|
||||||
COMMENT "Compile and test with the deprecated functions deleted"
|
|
||||||
)
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Disable global UDLs.
|
# Disable global UDLs.
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -376,7 +362,7 @@ add_custom_target(ci_test_coverage
|
|||||||
# Sanitizers.
|
# Sanitizers.
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
set(CLANG_CXX_FLAGS_SANITIZER "-g -O1 -fsanitize=address -fsanitize=undefined -fsanitize=integer -fsanitize=nullability -fno-omit-frame-pointer -fno-sanitize-recover=all -fno-sanitize=unsigned-integer-overflow -fno-sanitize=unsigned-shift-base -fsanitize-ignorelist=${PROJECT_SOURCE_DIR}/cmake/clang_sanitizer_ignorelist.txt")
|
set(CLANG_CXX_FLAGS_SANITIZER "-g -O1 -fsanitize=address -fsanitize=undefined -fsanitize=integer -fsanitize=nullability -fno-omit-frame-pointer -fno-sanitize-recover=all -fno-sanitize=unsigned-integer-overflow -fno-sanitize=unsigned-shift-base")
|
||||||
|
|
||||||
add_custom_target(ci_test_clang_sanitizer
|
add_custom_target(ci_test_clang_sanitizer
|
||||||
COMMAND CXX=${CLANG_TOOL} CXXFLAGS=${CLANG_CXX_FLAGS_SANITIZER} ${CMAKE_COMMAND}
|
COMMAND CXX=${CLANG_TOOL} CXXFLAGS=${CLANG_CXX_FLAGS_SANITIZER} ${CMAKE_COMMAND}
|
||||||
@@ -459,14 +445,13 @@ add_custom_target(ci_test_single_header
|
|||||||
# Valgrind.
|
# Valgrind.
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
# The Unicode test (~17M assertions) is too slow under Valgrind.
|
|
||||||
add_custom_target(ci_test_valgrind
|
add_custom_target(ci_test_valgrind
|
||||||
COMMAND CXX=${GCC_TOOL} ${CMAKE_COMMAND}
|
COMMAND CXX=${GCC_TOOL} ${CMAKE_COMMAND}
|
||||||
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||||
-DJSON_BuildTests=ON -DJSON_Valgrind=ON
|
-DJSON_BuildTests=ON -DJSON_Valgrind=ON
|
||||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_valgrind
|
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_valgrind
|
||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_valgrind
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_valgrind
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_valgrind && ${CMAKE_CTEST_COMMAND} -L valgrind --exclude-regex "test-unicode" --parallel ${N} --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_valgrind && ${CMAKE_CTEST_COMMAND} -L valgrind --parallel ${N} --output-on-failure
|
||||||
COMMENT "Compile and test with Valgrind"
|
COMMENT "Compile and test with Valgrind"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -723,7 +708,7 @@ ci_get_cmake(4.0.0 CMAKE_4_0_0_BINARY)
|
|||||||
# the tests require CMake 3.13 or later, so they are excluded for CMake 3.5.0
|
# the tests require CMake 3.13 or later, so they are excluded for CMake 3.5.0
|
||||||
set(JSON_CMAKE_FLAGS_3_5_0 JSON_Diagnostics JSON_Diagnostic_Positions JSON_GlobalUDLs JSON_ImplicitConversions JSON_DisableEnumSerialization
|
set(JSON_CMAKE_FLAGS_3_5_0 JSON_Diagnostics JSON_Diagnostic_Positions JSON_GlobalUDLs JSON_ImplicitConversions JSON_DisableEnumSerialization
|
||||||
JSON_LegacyDiscardedValueComparison JSON_Install JSON_MultipleHeaders JSON_SystemInclude JSON_Valgrind
|
JSON_LegacyDiscardedValueComparison JSON_Install JSON_MultipleHeaders JSON_SystemInclude JSON_Valgrind
|
||||||
JSON_StrictNulHandling JSON_StrictBinaryUTF8 JSON_DeleteDeprecatedFunctions)
|
JSON_StrictNulHandling JSON_StrictBinaryUTF8)
|
||||||
set(JSON_CMAKE_FLAGS_3_31_6 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0})
|
set(JSON_CMAKE_FLAGS_3_31_6 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0})
|
||||||
set(JSON_CMAKE_FLAGS_4_0_0 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0})
|
set(JSON_CMAKE_FLAGS_4_0_0 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0})
|
||||||
|
|
||||||
@@ -791,7 +776,7 @@ foreach(COMPILER g++-4.8 g++-4.9 g++-5 g++-6 g++-7 g++-8 g++-9 g++-10 g++-11 cla
|
|||||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_compiler_${COMPILER}
|
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_compiler_${COMPILER}
|
||||||
${ADDITIONAL_FLAGS}
|
${ADDITIONAL_FLAGS}
|
||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_compiler_${COMPILER}
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_compiler_${COMPILER}
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_compiler_${COMPILER} && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_compiler_${COMPILER} && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-unicode" --output-on-failure
|
||||||
COMMENT "Compile and test with ${COMPILER}"
|
COMMENT "Compile and test with ${COMPILER}"
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
@@ -805,7 +790,7 @@ add_custom_target(ci_test_compiler_default
|
|||||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_compiler_default
|
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_compiler_default
|
||||||
${ADDITIONAL_FLAGS}
|
${ADDITIONAL_FLAGS}
|
||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_compiler_default --parallel ${N}
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_compiler_default --parallel ${N}
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_compiler_default && ${CMAKE_CTEST_COMMAND} --parallel ${N} -LE git_required --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_compiler_default && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-unicode" -LE git_required --output-on-failure
|
||||||
COMMENT "Compile and test with default C++ compiler"
|
COMMENT "Compile and test with default C++ compiler"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -843,7 +828,7 @@ add_custom_target(ci_icpc
|
|||||||
-DJSON_BuildTests=ON -DJSON_FastTests=ON
|
-DJSON_BuildTests=ON -DJSON_FastTests=ON
|
||||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_icpc
|
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_icpc
|
||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_icpc
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_icpc
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_icpc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_icpc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-unicode" --output-on-failure
|
||||||
COMMENT "Compile and test with ICPC"
|
COMMENT "Compile and test with ICPC"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -854,7 +839,7 @@ add_custom_target(ci_icpx
|
|||||||
-DJSON_BuildTests=ON -DJSON_FastTests=ON
|
-DJSON_BuildTests=ON -DJSON_FastTests=ON
|
||||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_icpx
|
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_icpx
|
||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_icpx
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_icpx
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_icpx && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_icpx && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-unicode" --output-on-failure
|
||||||
COMMENT "Compile and test with ICPX (Intel oneAPI DPC++/C++)"
|
COMMENT "Compile and test with ICPX (Intel oneAPI DPC++/C++)"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -890,7 +875,7 @@ add_custom_target(ci_nvhpc
|
|||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_nvhpc
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_nvhpc
|
||||||
# the pipes are escaped so the surrounding shell passes them to ctest verbatim
|
# the pipes are escaped so the surrounding shell passes them to ctest verbatim
|
||||||
# instead of treating them as shell pipe operators
|
# instead of treating them as shell pipe operators
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_nvhpc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-comparison_cpp20\\|test-comparison_legacy_cpp20\\|test-constructor1_cpp11\\|test-deserialization_cpp20" --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_nvhpc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-unicode\\|test-comparison_cpp20\\|test-comparison_legacy_cpp20\\|test-constructor1_cpp11\\|test-deserialization_cpp20" --output-on-failure
|
||||||
COMMENT "Compile and test with NVIDIA HPC SDK (nvc++)"
|
COMMENT "Compile and test with NVIDIA HPC SDK (nvc++)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
# Sanitizer ignore list for ci_test_clang_sanitizer (-fsanitize-ignorelist).
|
|
||||||
#
|
|
||||||
# libstdc++ 14's <format> declares `_Scanner(basic_string_view<_CharT>, size_t __nargs = -1)`, so every std::format
|
|
||||||
# call converts -1 to size_t, which -fsanitize=integer reports as implicit-integer-sign-change. This is
|
|
||||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119429, not a bug in this library. Only that check and only <format> are
|
|
||||||
# excluded, so implicit sign changes in the library and the tests are still reported.
|
|
||||||
[implicit-integer-sign-change]
|
|
||||||
src:*/include/c++/*/format
|
|
||||||
@@ -48,7 +48,6 @@ cc_library(
|
|||||||
name = "singleheader-json",
|
name = "singleheader-json",
|
||||||
hdrs = [
|
hdrs = [
|
||||||
"single_include/nlohmann/json.hpp",
|
"single_include/nlohmann/json.hpp",
|
||||||
"single_include/nlohmann/json_fwd.hpp",
|
|
||||||
"single_include/nlohmann/json_view.hpp",
|
"single_include/nlohmann/json_view.hpp",
|
||||||
],
|
],
|
||||||
includes = ["single_include"],
|
includes = ["single_include"],
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ if(MODE STREQUAL "undef")
|
|||||||
# recipe is self-contained and its output is byte-stable across reruns.
|
# recipe is self-contained and its output is byte-stable across reruns.
|
||||||
# The embedded SPDX tags below are part of the *generated* file's
|
# The embedded SPDX tags below are part of the *generated* file's
|
||||||
# content, not a REUSE header for this .cmake script itself (which is
|
# content, not a REUSE header for this .cmake script itself (which is
|
||||||
# already covered by the blanket path = "**" rule in REUSE.toml) -- keep
|
# already covered by the blanket "Files: *" rule in .reuse/dep5) -- keep
|
||||||
# them wrapped in REUSE-IgnoreStart/End so `reuse lint` does not try to
|
# them wrapped in REUSE-IgnoreStart/End so `reuse lint` does not try to
|
||||||
# parse "MIT\n")" as this file's own SPDX-License-Identifier value.
|
# parse "MIT\n")" as this file's own SPDX-License-Identifier value.
|
||||||
# REUSE-IgnoreStart
|
# REUSE-IgnoreStart
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_string', 'Meth
|
|||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_ubjson', 'Function', 'api/basic_json/to_ubjson/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::to_ubjson', 'Function', 'api/basic_json/to_ubjson/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::value', 'Method', 'api/basic_json/value/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::value', 'Method', 'api/basic_json/value/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::value_t', 'Enum', 'api/basic_json/value_t/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::value_t', 'Enum', 'api/basic_json/value_t/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::with_t', 'Type', 'api/basic_json/with_t/index.html');
|
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::~basic_json', 'Method', 'api/basic_json/~basic_json/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::~basic_json', 'Method', 'api/basic_json/~basic_json/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document', 'Class', 'api/basic_json_document/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document', 'Class', 'api/basic_json_document/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::basic_json_document', 'Constructor', 'api/basic_json_document/basic_json_document/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::basic_json_document', 'Constructor', 'api/basic_json_document/basic_json_document/index.html');
|
||||||
@@ -292,7 +291,6 @@ INSERT INTO searchIndex(name, type, path) VALUES ('Supported Macros', 'Guide', '
|
|||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_ASSERT', 'Macro', 'api/macros/json_assert/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_ASSERT', 'Macro', 'api/macros/json_assert/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_BRACE_INIT_COPY_SEMANTICS', 'Macro', 'api/macros/json_brace_init_copy_semantics/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_BRACE_INIT_COPY_SEMANTICS', 'Macro', 'api/macros/json_brace_init_copy_semantics/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_CATCH_USER', 'Macro', 'api/macros/json_throw_user/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_CATCH_USER', 'Macro', 'api/macros/json_throw_user/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DELETE_DEPRECATED_FUNCTIONS', 'Macro', 'api/macros/json_delete_deprecated_functions/index.html');
|
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'api/macros/json_diagnostics/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'api/macros/json_diagnostics/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTIC_POSITIONS', 'Macro', 'api/macros/json_diagnostic_positions/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTIC_POSITIONS', 'Macro', 'api/macros/json_diagnostic_positions/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_ENUM_SERIALIZATION', 'Macro', 'api/macros/json_disable_enum_serialization/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_ENUM_SERIALIZATION', 'Macro', 'api/macros/json_disable_enum_serialization/index.html');
|
||||||
@@ -323,7 +321,6 @@ INSERT INTO searchIndex(name, type, path) VALUES ('JSON_TRY_USER', 'Macro', 'api
|
|||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_GLOBAL_UDLS', 'Macro', 'api/macros/json_use_global_udls/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_GLOBAL_UDLS', 'Macro', 'api/macros/json_use_global_udls/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_IMPLICIT_CONVERSIONS', 'Macro', 'api/macros/json_use_implicit_conversions/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_IMPLICIT_CONVERSIONS', 'Macro', 'api/macros/json_use_implicit_conversions/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON', 'Macro', 'api/macros/json_use_legacy_discarded_value_comparison/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON', 'Macro', 'api/macros/json_use_legacy_discarded_value_comparison/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS', 'Macro', 'api/macros/json_use_objects_for_enum_keyed_maps/index.html');
|
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_SIMDUTF', 'Macro', 'api/macros/json_use_simdutf/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_SIMDUTF', 'Macro', 'api/macros/json_use_simdutf/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('Macros', 'Macro', 'api/macros/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('Macros', 'Macro', 'api/macros/index.html');
|
||||||
INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html');
|
INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html');
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ enum class cbor_tag_handler_t
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
This enumeration is used in [`from_cbor`](from_cbor.md) and [`sax_parse`](sax_parse.md) to choose how to treat tags:
|
This enumeration is used in the [`from_cbor`](from_cbor.md) function to choose how to treat tags:
|
||||||
|
|
||||||
error
|
error
|
||||||
: report a parse error in case of a tag (the `from_cbor` overloads throw a `parse_error` exception by default)
|
: throw a `parse_error` exception in case of a tag
|
||||||
|
|
||||||
ignore
|
ignore
|
||||||
: ignore tags
|
: ignore tags
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ static basic_json diff(const basic_json& source,
|
|||||||
Creates a [JSON Patch](http://jsonpatch.com) so that value `source` can be changed into the value `target` by calling
|
Creates a [JSON Patch](http://jsonpatch.com) so that value `source` can be changed into the value `target` by calling
|
||||||
[`patch`](patch.md) function.
|
[`patch`](patch.md) function.
|
||||||
|
|
||||||
For two JSON values `source` and `target`, the following code always yields `#!cpp true`:
|
For two JSON values `source` and `target`, the following code yields always `#!cpp true`:
|
||||||
```cpp
|
```cpp
|
||||||
source.patch(diff(source, target)) == target;
|
source.patch(diff(source, target)) == target;
|
||||||
```
|
```
|
||||||
@@ -27,7 +27,7 @@ a JSON patch to convert the `source` to `target`
|
|||||||
|
|
||||||
## Exception safety
|
## Exception safety
|
||||||
|
|
||||||
Strong guarantee: `source` and `target` are never modified.
|
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -120,12 +120,3 @@ Linear in the size of the input.
|
|||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||||
- Added `error_handler` parameter in version 3.13.0.
|
- Added `error_handler` parameter in version 3.13.0.
|
||||||
|
|
||||||
!!! warning "Deprecation"
|
|
||||||
|
|
||||||
- Overload (2) replaces calls to `from_bjdata` with a pointer and a length as first two parameters, which has been
|
|
||||||
deprecated in version 3.13.0. This overload will be removed in version 4.0.0. Please replace all calls like
|
|
||||||
`#!cpp from_bjdata(ptr, len, ...);` with `#!cpp from_bjdata(ptr, ptr+len, ...);`.
|
|
||||||
|
|
||||||
You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated
|
|
||||||
function.
|
|
||||||
@@ -106,12 +106,3 @@ Linear in the size of the input.
|
|||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
- Added in version 3.13.0.
|
- Added in version 3.13.0.
|
||||||
|
|
||||||
!!! warning "Deprecation"
|
|
||||||
|
|
||||||
- Overload (2) replaces calls to `from_bon8` with a pointer and a length as first two parameters, which has been
|
|
||||||
deprecated in version 3.13.0. This overload will be removed in version 4.0.0. Please replace all calls like
|
|
||||||
`#!cpp from_bon8(ptr, len, ...);` with `#!cpp from_bon8(ptr, ptr+len, ...);`.
|
|
||||||
|
|
||||||
You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated
|
|
||||||
function.
|
|
||||||
@@ -109,9 +109,6 @@ The class satisfies the following concept requirements:
|
|||||||
- **initializer_list_t** - type for initializer lists of `basic_json` values
|
- **initializer_list_t** - type for initializer lists of `basic_json` values
|
||||||
- [**input_format_t**](input_format_t.md) - type to choose the format to parse
|
- [**input_format_t**](input_format_t.md) - type to choose the format to parse
|
||||||
- [**json_sax_t**](../json_sax/index.md) - type for SAX events
|
- [**json_sax_t**](../json_sax/index.md) - type for SAX events
|
||||||
- [**with_object_t, with_array_t, with_string_t, with_boolean_t, with_integers_t, with_float_t, with_allocator_t,
|
|
||||||
with_json_serializer_t, with_binary_t, with_base_class_t**](with_t.md) - types to create a `basic_json` type with
|
|
||||||
one (or two) replaced template parameters
|
|
||||||
|
|
||||||
### Exceptions
|
### Exceptions
|
||||||
|
|
||||||
|
|||||||
@@ -56,10 +56,6 @@ This implementation does exactly follow this approach, as it uses double precisi
|
|||||||
smaller than `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` will be stored as NaN internally
|
smaller than `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` will be stored as NaN internally
|
||||||
and be serialized to `null`.
|
and be serialized to `null`.
|
||||||
|
|
||||||
During deserialization (from JSON text or any of the binary formats), a finite number that does not fit into
|
|
||||||
`number_float_t` is rejected with [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406), for
|
|
||||||
example a double-precision number in a binary format when `number_float_t` is `#!cpp float`.
|
|
||||||
|
|
||||||
### Storage
|
### Storage
|
||||||
|
|
||||||
Floating-point number values are stored directly inside a `basic_json` type.
|
Floating-point number values are stored directly inside a `basic_json` type.
|
||||||
|
|||||||
@@ -47,9 +47,8 @@ With the default values for `NumberIntegerType` (`std::int64_t`), the default va
|
|||||||
|
|
||||||
When the default type is used, the maximal integer number that can be stored is `9223372036854775807` (INT64_MAX) and
|
When the default type is used, the maximal integer number that can be stored is `9223372036854775807` (INT64_MAX) and
|
||||||
the minimal integer number that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers that are out of
|
the minimal integer number that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers that are out of
|
||||||
range will yield over/underflow when used in a constructor. During deserialization (from JSON text or any of the binary
|
range will yield over/underflow when used in a constructor. During deserialization, too large or small integer numbers
|
||||||
formats), too large or small integer numbers will automatically be stored as [`number_unsigned_t`](number_unsigned_t.md)
|
will automatically be stored as [`number_unsigned_t`](number_unsigned_t.md) or [`number_float_t`](number_float_t.md).
|
||||||
or [`number_float_t`](number_float_t.md).
|
|
||||||
|
|
||||||
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
||||||
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
|
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
|
||||||
|
|||||||
@@ -48,9 +48,8 @@ With the default values for `NumberUnsignedType` (`std::uint64_t`), the default
|
|||||||
|
|
||||||
When the default type is used, the maximal integer number that can be stored is `18446744073709551615` (UINT64_MAX) and
|
When the default type is used, the maximal integer number that can be stored is `18446744073709551615` (UINT64_MAX) and
|
||||||
the minimal integer number that can be stored is `0`. Integer numbers that are out of range will yield over/underflow
|
the minimal integer number that can be stored is `0`. Integer numbers that are out of range will yield over/underflow
|
||||||
when used in a constructor. During deserialization (from JSON text or any of the binary formats), too large or small
|
when used in a constructor. During deserialization, too large or small integer numbers will automatically be stored
|
||||||
integer numbers will automatically be stored as [`number_integer_t`](number_integer_t.md) or
|
as [`number_integer_t`](number_integer_t.md) or [`number_float_t`](number_float_t.md).
|
||||||
[`number_float_t`](number_float_t.md).
|
|
||||||
|
|
||||||
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
|
||||||
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
|
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ static bool sax_parse(InputType&& i,
|
|||||||
input_format_t format = input_format_t::json,
|
input_format_t format = input_format_t::json,
|
||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
const bool ignore_trailing_commas = false,
|
const bool ignore_trailing_commas = false);
|
||||||
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error);
|
|
||||||
|
|
||||||
// (2)
|
// (2)
|
||||||
template<class IteratorType, class SAX, class SentinelType = IteratorType>
|
template<class IteratorType, class SAX, class SentinelType = IteratorType>
|
||||||
@@ -18,14 +17,13 @@ static bool sax_parse(IteratorType first, SentinelType last,
|
|||||||
input_format_t format = input_format_t::json,
|
input_format_t format = input_format_t::json,
|
||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
const bool ignore_trailing_commas = false,
|
const bool ignore_trailing_commas = false);
|
||||||
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Read from input and generate SAX events
|
Read from input and generate SAX events
|
||||||
|
|
||||||
1. Read from a compatible input.
|
1. Read from a compatible input.
|
||||||
2. Read from a pair of character iterators, or an iterator and a sentinel of a different type (C++20 ranges support).
|
2. Read from a pair of character iterators, or an iterator and a sentinel of a different type (C++20 ranges support)
|
||||||
|
|
||||||
The value_type of the iterator must be an integral type with a size of 1, 2, or 4 bytes, which will be interpreted
|
The value_type of the iterator must be an integral type with a size of 1, 2, or 4 bytes, which will be interpreted
|
||||||
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
|
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
|
||||||
@@ -84,10 +82,6 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
|||||||
: whether trailing commas in arrays or objects should be ignored and treated like whitespace (`#!cpp true`) or yield a parse error
|
: whether trailing commas in arrays or objects should be ignored and treated like whitespace (`#!cpp true`) or yield a parse error
|
||||||
(`#!cpp false`); (optional, `#!cpp false` by default)
|
(`#!cpp false`); (optional, `#!cpp false` by default)
|
||||||
|
|
||||||
`tag_handler` (in)
|
|
||||||
: how to handle CBOR tags; see [`cbor_tag_handler_t`](cbor_tag_handler_t.md). Ignored for formats other than CBOR
|
|
||||||
(optional, `cbor_tag_handler_t::error` by default).
|
|
||||||
|
|
||||||
`first` (in)
|
`first` (in)
|
||||||
: iterator to the start of a character range
|
: iterator to the start of a character range
|
||||||
|
|
||||||
@@ -143,7 +137,6 @@ A UTF-8 byte order mark is silently ignored.
|
|||||||
- Added in version 3.2.0.
|
- Added in version 3.2.0.
|
||||||
- Ignoring comments via `ignore_comments` added in version 3.9.0.
|
- Ignoring comments via `ignore_comments` added in version 3.9.0.
|
||||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||||
- Added `tag_handler` in version 3.13.0.
|
|
||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||||
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
|
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
|
||||||
|
|||||||
@@ -7,14 +7,8 @@ namespace std {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Return a hash value for a JSON object. The hash function tries to rely on `std::hash` where possible. Furthermore, the
|
Return a hash value for a JSON object. The hash function tries to rely on `std::hash` where possible. Furthermore, the
|
||||||
type of the JSON value is taken into account, so `#!json null`, `#!cpp false`, and numbers may hash differently from
|
type of the JSON value is taken into account to have different hash values for `#!json null`, `#!cpp 0`, `#!cpp 0U`, and
|
||||||
each other. Numbers that compare equal under [`operator==`](operator_eq.md) always hash equally, regardless of
|
`#!cpp false`, etc.
|
||||||
whether they are stored as signed integer, unsigned integer, or floating-point number.
|
|
||||||
|
|
||||||
Numbers are hashed by their value converted to `number_float_t`. Converting an integer to `number_float_t` therefore
|
|
||||||
keeps its hash, but converting a floating-point number to an integer type is lossy and can change it: `#!cpp 0.5`
|
|
||||||
converts to `#!cpp 0`, which need not have the same hash. Unequal numbers can also share a hash value, for example two
|
|
||||||
large integers that convert to the same `number_float_t`.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -32,8 +26,7 @@ large integers that convert to the same `number_float_t`.
|
|||||||
--8<-- "examples/std_hash.output"
|
--8<-- "examples/std_hash.output"
|
||||||
```
|
```
|
||||||
|
|
||||||
The hash values shown are examples only. They depend on the platform, the compiler, and the compiler version, and
|
Note the output is platform-dependent.
|
||||||
they can change between versions of this library. Do not persist them or rely on specific values.
|
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
@@ -43,5 +36,3 @@ large integers that convert to the same `number_float_t`.
|
|||||||
|
|
||||||
- Added in version 1.0.0.
|
- Added in version 1.0.0.
|
||||||
- Extended for arbitrary basic_json types in version 3.10.5.
|
- Extended for arbitrary basic_json types in version 3.10.5.
|
||||||
- Numbers that compare equal hash equally since version 3.13.0; before, `#!cpp 0`, `#!cpp 0U`, and `#!cpp 0.0` had
|
|
||||||
different hash values.
|
|
||||||
@@ -68,8 +68,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
|
||||||
discarded; example: `"cannot serialize discarded value to BJData"`
|
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -122,5 +120,3 @@ Linear in the size of the JSON value `j`.
|
|||||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
|
||||||
array or object was silently skipped, producing invalid BJData.
|
|
||||||
@@ -58,9 +58,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is
|
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is
|
||||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if a value nested in `j` is discarded
|
|
||||||
(the top-level value itself is covered by `type_error.317` above, since it must be an object); example:
|
|
||||||
`"cannot serialize discarded value to BSON"`
|
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -113,8 +110,6 @@ pass before anything is written.
|
|||||||
- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0.
|
- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0.
|
||||||
- Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0.
|
- Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0.
|
||||||
- `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0.
|
- `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0.
|
||||||
- Throws `type_error.321` for a discarded value nested in `j` since version 3.13.0; previously, it was silently
|
|
||||||
skipped, producing a document whose declared size did not match what was actually written.
|
|
||||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything
|
||||||
|
|||||||
@@ -49,8 +49,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
|
||||||
discarded; example: `"cannot serialize discarded value to CBOR"`
|
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -88,5 +86,3 @@ Linear in the size of the JSON value `j`.
|
|||||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
|
||||||
array or object was silently skipped, producing invalid CBOR.
|
|
||||||
@@ -54,8 +54,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
|
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
|
||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||||
not valid UTF-8 and `error_handler` is `strict`
|
not valid UTF-8 and `error_handler` is `strict`
|
||||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
|
||||||
discarded; example: `"cannot serialize discarded value to MessagePack"`
|
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -110,5 +108,3 @@ Linear in the size of the JSON value `j`.
|
|||||||
- Fixed in version 3.13.0 to serialize `number_integer_t`/`number_unsigned_t` pairs of different width correctly;
|
- Fixed in version 3.13.0 to serialize `number_integer_t`/`number_unsigned_t` pairs of different width correctly;
|
||||||
before, integers could be serialized with the wrong value if `number_integer_t` was narrower than
|
before, integers could be serialized with the wrong value if `number_integer_t` was narrower than
|
||||||
`number_unsigned_t`.
|
`number_unsigned_t`.
|
||||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
|
||||||
array or object was silently skipped, producing invalid MessagePack.
|
|
||||||
@@ -61,8 +61,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
|
||||||
discarded; example: `"cannot serialize discarded value to UBJSON"`
|
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -114,5 +112,3 @@ Linear in the size of the JSON value `j`.
|
|||||||
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
|
||||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
|
||||||
array or object was silently skipped, producing invalid UBJSON.
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
# <small>nlohmann::basic_json::</small>with_t
|
|
||||||
|
|
||||||
Member alias templates `with_object_t`, `with_array_t`, `with_string_t`, `with_boolean_t`, `with_integers_t`,
|
|
||||||
`with_float_t`, `with_allocator_t`, `with_json_serializer_t`, `with_binary_t`, and `with_base_class_t`.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
template<template<typename, typename, typename...> class ObjectType2>
|
|
||||||
using with_object_t = basic_json<ObjectType2, ArrayType, StringType, BooleanType,
|
|
||||||
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
|
||||||
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
|
||||||
|
|
||||||
template<template<typename, typename...> class ArrayType2>
|
|
||||||
using with_array_t = basic_json<ObjectType, ArrayType2, StringType, BooleanType,
|
|
||||||
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
|
||||||
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
|
||||||
|
|
||||||
template<class StringType2>
|
|
||||||
using with_string_t = basic_json<ObjectType, ArrayType, StringType2, BooleanType,
|
|
||||||
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
|
||||||
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
|
||||||
|
|
||||||
template<class BooleanType2>
|
|
||||||
using with_boolean_t = basic_json<ObjectType, ArrayType, StringType, BooleanType2,
|
|
||||||
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
|
||||||
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
|
||||||
|
|
||||||
template<class NumberIntegerType2, class NumberUnsignedType2>
|
|
||||||
using with_integers_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
|
||||||
NumberIntegerType2, NumberUnsignedType2, NumberFloatType,
|
|
||||||
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
|
||||||
|
|
||||||
template<class NumberFloatType2>
|
|
||||||
using with_float_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
|
||||||
NumberIntegerType, NumberUnsignedType, NumberFloatType2,
|
|
||||||
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>;
|
|
||||||
|
|
||||||
template<template<typename> class AllocatorType2>
|
|
||||||
using with_allocator_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
|
||||||
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
|
||||||
AllocatorType2, JSONSerializer, BinaryType, CustomBaseClass>;
|
|
||||||
|
|
||||||
template<template<typename, typename = void> class JSONSerializer2>
|
|
||||||
using with_json_serializer_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
|
||||||
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
|
||||||
AllocatorType, JSONSerializer2, BinaryType, CustomBaseClass>;
|
|
||||||
|
|
||||||
template<class BinaryType2>
|
|
||||||
using with_binary_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
|
||||||
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
|
||||||
AllocatorType, JSONSerializer, BinaryType2, CustomBaseClass>;
|
|
||||||
|
|
||||||
template<class CustomBaseClass2>
|
|
||||||
using with_base_class_t = basic_json<ObjectType, ArrayType, StringType, BooleanType,
|
|
||||||
NumberIntegerType, NumberUnsignedType, NumberFloatType,
|
|
||||||
AllocatorType, JSONSerializer, BinaryType, CustomBaseClass2>;
|
|
||||||
```
|
|
||||||
|
|
||||||
These member alias templates make it easier to create a `basic_json` type that is identical to the current type except
|
|
||||||
for one (or, in the case of `with_integers_t`, two) of its [template parameters](index.md#template-parameters).
|
|
||||||
Spelling out all 11 template parameters of `basic_json` just to change a single one is verbose and error-prone; these
|
|
||||||
aliases only require the replacement type(s).
|
|
||||||
|
|
||||||
with_object_t<ObjectType2>
|
|
||||||
: replaces `ObjectType`
|
|
||||||
|
|
||||||
with_array_t<ArrayType2>
|
|
||||||
: replaces `ArrayType`
|
|
||||||
|
|
||||||
with_string_t<StringType2>
|
|
||||||
: replaces `StringType`
|
|
||||||
|
|
||||||
with_boolean_t<BooleanType2>
|
|
||||||
: replaces `BooleanType`
|
|
||||||
|
|
||||||
with_integers_t<NumberIntegerType2, NumberUnsignedType2>
|
|
||||||
: replaces both `NumberIntegerType` and `NumberUnsignedType`; the two are combined into a single alias because they
|
|
||||||
are usually changed together (for instance, when switching to fixed-width integer types)
|
|
||||||
|
|
||||||
with_float_t<NumberFloatType2>
|
|
||||||
: replaces `NumberFloatType`
|
|
||||||
|
|
||||||
with_allocator_t<AllocatorType2>
|
|
||||||
: replaces `AllocatorType`
|
|
||||||
|
|
||||||
with_json_serializer_t<JSONSerializer2>
|
|
||||||
: replaces `JSONSerializer`
|
|
||||||
|
|
||||||
with_binary_t<BinaryType2>
|
|
||||||
: replaces `BinaryType`
|
|
||||||
|
|
||||||
with_base_class_t<CustomBaseClass2>
|
|
||||||
: replaces `CustomBaseClass`; see also [`json_base_class_t`](json_base_class_t.md)
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
All other template parameters are kept unchanged, so the resulting type still uses, for instance, the same
|
|
||||||
`ObjectType` unless `with_object_t` itself is used.
|
|
||||||
|
|
||||||
The aliases are members of every `basic_json` specialization, including [`ordered_json`](../ordered_json.md), and the
|
|
||||||
type they produce is again a `basic_json` specialization. They can therefore be chained to replace several template
|
|
||||||
parameters at once:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
using my_json = nlohmann::json::with_integers_t<int, unsigned int>::with_float_t<float>;
|
|
||||||
using my_ordered_json = nlohmann::ordered_json::with_string_t<std::wstring>;
|
|
||||||
```
|
|
||||||
|
|
||||||
The result is the same type as spelling out all template parameters, so the order of the chained aliases does not
|
|
||||||
matter. For instance, `nlohmann::json::with_object_t<nlohmann::ordered_map>` is `nlohmann::ordered_json`.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
??? example
|
|
||||||
|
|
||||||
The following code shows how `with_object_t` can be used to create a JSON type that stores object elements in a
|
|
||||||
`std::map` and therefore keeps them sorted by key, unlike the default type which preserves insertion order
|
|
||||||
only when `nlohmann::ordered_json` is used.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
--8<-- "examples/with_t.cpp"
|
|
||||||
```
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```json
|
|
||||||
--8<-- "examples/with_t.output"
|
|
||||||
```
|
|
||||||
|
|
||||||
## See also
|
|
||||||
|
|
||||||
- [basic_json](index.md#template-parameters) - the template parameters that can be replaced
|
|
||||||
- [json_base_class_t](json_base_class_t.md) - the type used for `CustomBaseClass`
|
|
||||||
|
|
||||||
## Version history
|
|
||||||
|
|
||||||
- Added in version 3.13.0.
|
|
||||||
@@ -60,13 +60,6 @@ header. See also the [macro overview page](../../features/macros.md).
|
|||||||
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
|
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
|
||||||
- [**JSON_DISABLE_TUPLE_REFERENCE_CONVERSION**](json_disable_tuple_reference_conversion.md) - switch off conversion from a one-element tuple of a JSON reference
|
- [**JSON_DISABLE_TUPLE_REFERENCE_CONVERSION**](json_disable_tuple_reference_conversion.md) - switch off conversion from a one-element tuple of a JSON reference
|
||||||
- [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions
|
- [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions
|
||||||
- [**JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS**](json_use_objects_for_enum_keyed_maps.md) - opt in to storing maps with enum
|
|
||||||
keys as objects
|
|
||||||
|
|
||||||
## Deprecated functions
|
|
||||||
|
|
||||||
- [**JSON_DELETE_DEPRECATED_FUNCTIONS**](json_delete_deprecated_functions.md) - opt in to deleting the deprecated
|
|
||||||
functions ahead of their removal in version 4.0.0
|
|
||||||
|
|
||||||
## Comparison behavior
|
## Comparison behavior
|
||||||
|
|
||||||
|
|||||||
@@ -50,11 +50,9 @@ The default value is `0` (disabled — existing behavior is preserved).
|
|||||||
```
|
```
|
||||||
|
|
||||||
Code that relies on these producing arrays must use `json::array()` instead (see below). Lists with more than one
|
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 *written as a braced list*, such as `{{"key", "value"}}`, which still
|
element, and a single `[string, value]` pair such as `{{"key", "value"}}`, which still creates an object, are not
|
||||||
creates an object, are not affected. This exception is based on how the pair is written, not on the shape of its
|
affected. The library's own conversions are not affected either: for example, `std::tuple<int>{5}` still becomes
|
||||||
value: an existing JSON value that happens to be a two-element array with a string as its first element, such as
|
`[5]`.
|
||||||
`json arr = {"key", 42};`, is still copied by `json j{arr};` rather than turned into an object. The library's own
|
|
||||||
conversions are not affected either: for example, `std::tuple<int>{5}` still becomes `[5]`.
|
|
||||||
|
|
||||||
!!! note "ABI compatibility"
|
!!! note "ABI compatibility"
|
||||||
|
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
# JSON_DELETE_DEPRECATED_FUNCTIONS
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define JSON_DELETE_DEPRECATED_FUNCTIONS /* value */
|
|
||||||
```
|
|
||||||
|
|
||||||
When defined to `1`, all [deprecated functions](../../community/roadmap.md#removal-of-deprecated-functions) of the
|
|
||||||
library are declared as deleted (`= delete`) instead of only being marked as deprecated. Code that still calls one of
|
|
||||||
them no longer compiles. This way, you can find all calls that need to be replaced before version 4.0.0 removes these
|
|
||||||
functions; the [migration guide](../../integration/migration_guide.md#replace-deprecated-functions) describes how.
|
|
||||||
|
|
||||||
A deleted function, unlike a removed one, still takes part in overload resolution. A call that would select it
|
|
||||||
therefore fails to compile instead of silently selecting another overload. This matters for the deprecated
|
|
||||||
`from_*(ptr, len)` overloads of [`from_cbor`](../basic_json/from_cbor.md), [`from_msgpack`](../basic_json/from_msgpack.md),
|
|
||||||
[`from_ubjson`](../basic_json/from_ubjson.md), [`from_bjdata`](../basic_json/from_bjdata.md),
|
|
||||||
[`from_bon8`](../basic_json/from_bon8.md), and [`from_bson`](../basic_json/from_bson.md): without them, a call like
|
|
||||||
`from_cbor(ptr, len)` would compile, read `ptr` as a NUL-terminated string, and convert `len` to the `strict` parameter.
|
|
||||||
|
|
||||||
The macro does not affect the deprecated legacy comparison of discarded values, which is controlled by
|
|
||||||
[`JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](json_use_legacy_discarded_value_comparison.md).
|
|
||||||
|
|
||||||
## Default definition
|
|
||||||
|
|
||||||
The default value is `0` (disabled, the deprecated functions can still be called, and the compiler warns about it).
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define JSON_DELETE_DEPRECATED_FUNCTIONS 0
|
|
||||||
```
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
!!! info "CMake option"
|
|
||||||
|
|
||||||
The macro can also be set with the CMake option
|
|
||||||
[`JSON_DeleteDeprecatedFunctions`](../../integration/cmake.md#json_deletedeprecatedfunctions) (`OFF` by default).
|
|
||||||
|
|
||||||
!!! warning "Opt-in only"
|
|
||||||
|
|
||||||
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no
|
|
||||||
effect. Define it for the whole project to avoid different declarations of the same class in different
|
|
||||||
translation units.
|
|
||||||
|
|
||||||
!!! note "ABI compatibility"
|
|
||||||
|
|
||||||
The macro only turns calls that compile into calls that do not; it does not change the layout or the behavior of
|
|
||||||
any type. Its value is therefore not encoded in the [namespace](../../features/namespace.md).
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
??? example "Example: default behavior (macro not defined)"
|
|
||||||
|
|
||||||
Without the macro, the deprecated overload is called, and the compiler warns about it:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
const std::vector<std::uint8_t> v = {0x82, 0x01, 0x02};
|
|
||||||
auto j = json::from_cbor(v.data(), v.size());
|
|
||||||
// warning: 'from_cbor' is deprecated: Since 3.8.0; use from_cbor(ptr, ptr + len)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
??? example "Example: deleted deprecated functions (macro defined to 1)"
|
|
||||||
|
|
||||||
With the macro, the call does not compile:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define JSON_DELETE_DEPRECATED_FUNCTIONS 1
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
const std::vector<std::uint8_t> v = {0x82, 0x01, 0x02};
|
|
||||||
auto j = json::from_cbor(v.data(), v.size());
|
|
||||||
// error: call to deleted function 'from_cbor'
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## See also
|
|
||||||
|
|
||||||
- [Roadmap: removal of deprecated functions](../../community/roadmap.md#removal-of-deprecated-functions) - the
|
|
||||||
deprecated functions and the version they were deprecated in
|
|
||||||
- [Migration guide: replace deprecated functions](../../integration/migration_guide.md#replace-deprecated-functions) -
|
|
||||||
how to replace each deprecated function
|
|
||||||
|
|
||||||
## Version history
|
|
||||||
|
|
||||||
- Added in version 3.13.0.
|
|
||||||
- Planned to be removed in version 4.0.0, which removes the deprecated functions. The deprecated `from_*(ptr, len)`
|
|
||||||
overloads stay deleted in version 4.0.0.
|
|
||||||
@@ -112,4 +112,3 @@ The default value is `0` (disabled — existing behavior is preserved).
|
|||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
- Added in version 3.13.0.
|
- Added in version 3.13.0.
|
||||||
- Planned to become the default (with the macro removed) in version 4.0.0.
|
|
||||||
@@ -44,7 +44,7 @@ By default, implicit conversions are enabled.
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
??? example "Example: implicit and explicit conversions"
|
??? example "Example: implicit conversion"
|
||||||
|
|
||||||
This is an example for an implicit conversion:
|
This is an example for an implicit conversion:
|
||||||
|
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
# JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS /* value */
|
|
||||||
```
|
|
||||||
|
|
||||||
When defined to `1`, maps whose keys are enums (such as `std::map<E, T>` or `std::unordered_map<E, T>`) are stored as
|
|
||||||
JSON objects, using the enum's own conversion for the keys. By default, they are stored as arrays of `[key, value]`
|
|
||||||
pairs.
|
|
||||||
|
|
||||||
## Default definition
|
|
||||||
|
|
||||||
The default value is `0` (disabled — existing behavior is preserved).
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
|
|
||||||
```
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
!!! note "Background"
|
|
||||||
|
|
||||||
JSON object keys are strings, so a map is only stored as an object if its keys can be converted to a string type.
|
|
||||||
Enums are not, even if [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) maps them to strings, so a
|
|
||||||
map with enum keys becomes an array of `[key, value]` pairs:
|
|
||||||
|
|
||||||
```json
|
|
||||||
[["stopped", "aa"], ["completed", "bb"]]
|
|
||||||
```
|
|
||||||
|
|
||||||
With this macro, the same map becomes an object
|
|
||||||
(see [#4378](https://github.com/nlohmann/json/issues/4378)):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"completed": "bb", "stopped": "aa"}
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! note "Maps with non-unique keys"
|
|
||||||
|
|
||||||
Maps that allow duplicate keys, such as `std::multimap<E, T>` or `std::unordered_multimap<E, T>`, are not affected
|
|
||||||
by the macro and are still stored as arrays of `[key, value]` pairs, as an object cannot hold duplicate keys.
|
|
||||||
|
|
||||||
!!! note "Reading"
|
|
||||||
|
|
||||||
Reading is not affected by the macro: a map with enum keys can always be read from both an array of pairs and an
|
|
||||||
object. For the latter, each key is converted to the enum with its `from_json` function, e.g., the one defined by
|
|
||||||
[`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md). Data written without the macro can therefore
|
|
||||||
still be read after enabling it.
|
|
||||||
|
|
||||||
!!! warning "Keys must serialize to distinct strings"
|
|
||||||
|
|
||||||
Each key is converted with the enum's `to_json` function. If a key is not converted to a string (for instance, an
|
|
||||||
enum without [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md), which is stored as an integer, or an
|
|
||||||
enumerator mapped to `nullptr`), [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) is thrown.
|
|
||||||
If two keys are converted to the same string (for instance, because
|
|
||||||
[`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) maps an unlisted enumerator to the first entry),
|
|
||||||
[`type_error.318`](../../home/exceptions.md#jsonexceptiontype_error318) is thrown. In both cases, the target value
|
|
||||||
is not changed.
|
|
||||||
|
|
||||||
!!! warning "Opt-in only"
|
|
||||||
|
|
||||||
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no effect.
|
|
||||||
|
|
||||||
!!! note "ABI compatibility"
|
|
||||||
|
|
||||||
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_ekmo`), 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.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
??? example "Example: default behavior (macro not defined)"
|
|
||||||
|
|
||||||
Without the macro, a map with enum keys is stored as an array of pairs:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include <map>
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
enum TaskState { TS_STOPPED, TS_RUNNING, TS_COMPLETED };
|
|
||||||
|
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, {
|
|
||||||
{TS_STOPPED, "stopped"},
|
|
||||||
{TS_RUNNING, "running"},
|
|
||||||
{TS_COMPLETED, "completed"},
|
|
||||||
})
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
std::map<TaskState, std::string> m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
|
|
||||||
|
|
||||||
json j = m;
|
|
||||||
// j is [["stopped","aa"],["completed","bb"]]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
??? example "Example: objects for enum-keyed maps (macro defined to 1)"
|
|
||||||
|
|
||||||
With the macro, the same map is stored as an object:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 1
|
|
||||||
#include <map>
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
enum TaskState { TS_STOPPED, TS_RUNNING, TS_COMPLETED };
|
|
||||||
|
|
||||||
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, {
|
|
||||||
{TS_STOPPED, "stopped"},
|
|
||||||
{TS_RUNNING, "running"},
|
|
||||||
{TS_COMPLETED, "completed"},
|
|
||||||
})
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
std::map<TaskState, std::string> m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
|
|
||||||
|
|
||||||
json j = m;
|
|
||||||
// j is {"completed":"bb","stopped":"aa"}
|
|
||||||
|
|
||||||
auto m2 = j.get<std::map<TaskState, std::string>>();
|
|
||||||
// m2 == m
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## See also
|
|
||||||
|
|
||||||
- [Specializing enum conversion](../../features/enum_conversion.md)
|
|
||||||
- [**NLOHMANN_JSON_SERIALIZE_ENUM**](nlohmann_json_serialize_enum.md) - serialize/deserialize an enum
|
|
||||||
- [**NLOHMANN_JSON_SERIALIZE_ENUM_STRICT**](nlohmann_json_serialize_enum_strict.md) - serialize/deserialize an enum with
|
|
||||||
exceptions
|
|
||||||
|
|
||||||
## Version history
|
|
||||||
|
|
||||||
- Added in version 3.13.0.
|
|
||||||
@@ -41,9 +41,6 @@ inline void from_json(const BasicJsonType& j, type& e);
|
|||||||
conversion. Select this default pair carefully. See example 1 below.
|
conversion. Select this default pair carefully. See example 1 below.
|
||||||
- If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the
|
- If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the
|
||||||
list will be returned when converting to or from JSON. See example 2 below.
|
list will be returned when converting to or from JSON. See example 2 below.
|
||||||
- Maps with enum keys (e.g., `std::map<ENUM_TYPE, T>`) are stored as arrays of `[key, value]` pairs by default.
|
|
||||||
Define [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) to store them as objects
|
|
||||||
with the converted keys. Such maps can be read from both forms.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -83,7 +80,6 @@ inline void from_json(const BasicJsonType& j, type& e);
|
|||||||
- [Specializing enum conversion](../../features/enum_conversion.md)
|
- [Specializing enum conversion](../../features/enum_conversion.md)
|
||||||
- [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](./nlohmann_json_serialize_enum_strict.md)
|
- [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](./nlohmann_json_serialize_enum_strict.md)
|
||||||
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
|
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
|
||||||
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md)
|
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -44,9 +44,6 @@ inline void from_json(const BasicJsonType& j, type& e);
|
|||||||
`"enum value out of range for <type>"`.
|
`"enum value out of range for <type>"`.
|
||||||
- If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the
|
- If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the
|
||||||
list will be returned when converting to or from JSON. See example 2 below.
|
list will be returned when converting to or from JSON. See example 2 below.
|
||||||
- Maps with enum keys (e.g., `std::map<ENUM_TYPE, T>`) are stored as arrays of `[key, value]` pairs by default.
|
|
||||||
Define [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) to store them as objects
|
|
||||||
with the converted keys. Such maps can be read from both forms.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -102,7 +99,6 @@ inline void from_json(const BasicJsonType& j, type& e);
|
|||||||
- [Specializing enum conversion](../../features/enum_conversion.md)
|
- [Specializing enum conversion](../../features/enum_conversion.md)
|
||||||
- [`NLOHMANN_JSON_SERIALIZE_ENUM`](./nlohmann_json_serialize_enum.md)
|
- [`NLOHMANN_JSON_SERIALIZE_ENUM`](./nlohmann_json_serialize_enum.md)
|
||||||
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
|
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
|
||||||
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md)
|
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -21,18 +21,17 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
|||||||
|
|
||||||
| Compiler | Architecture | Operating System | CI |
|
| Compiler | Architecture | Operating System | CI |
|
||||||
|----------------------------------------------|--------------|-----------------------------------|-----------|
|
|----------------------------------------------|--------------|-----------------------------------|-----------|
|
||||||
|
| AppleClang 15.0.0.15000040; Xcode 15.0.1 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||||
|
| AppleClang 15.0.0.15000100; Xcode 15.1 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||||
|
| AppleClang 15.0.0.15000100; Xcode 15.2 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||||
|
| AppleClang 15.0.0.15000309; Xcode 15.3 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||||
|
| AppleClang 15.0.0.15000309; Xcode 15.4 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
|
||||||
| AppleClang 16.0.0.16000026; Xcode 16 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
| AppleClang 16.0.0.16000026; Xcode 16 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||||
| AppleClang 16.0.0.16000026; Xcode 16.1 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
| AppleClang 16.0.0.16000026; Xcode 16.1 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||||
| AppleClang 16.0.0.16000026; Xcode 16.2 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
| AppleClang 16.0.0.16000026; Xcode 16.2 | arm64 | macOS 15.2 (Sequoia) | GitHub |
|
||||||
| AppleClang 17.0.0.17000013; Xcode 16.3 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
| AppleClang 17.0.0.17000013; Xcode 16.3 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
||||||
| AppleClang 17.0.0.17000013; Xcode 16.4 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
| AppleClang 17.0.0.17000013; Xcode 16.4 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
||||||
| AppleClang 17.0.0.17000319; Xcode 26.0.1 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
| AppleClang 17.0.0.17000319; Xcode 26.0.1 | arm64 | macOS 15.5 (Sequoia) | GitHub |
|
||||||
| AppleClang 17.0.0.17000404; Xcode 26.1.1 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
|
|
||||||
| AppleClang 17.0.0.17000603; Xcode 26.2 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
|
|
||||||
| AppleClang 17.0.0.17000604; Xcode 26.3 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
|
|
||||||
| AppleClang 21.0.0.21000099; Xcode 26.4.1 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
|
|
||||||
| AppleClang 21.0.0.21000101; Xcode 26.5 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
|
|
||||||
| AppleClang 21.0.0.21000101; Xcode 26.6 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
|
|
||||||
| Clang 3.4.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| Clang 3.4.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| Clang 3.5.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| Clang 3.5.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| Clang 3.6.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| Clang 3.6.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
@@ -90,7 +89,7 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
|||||||
| GNU 13.3.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| GNU 13.3.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| GNU 14.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| GNU 14.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| GNU 15.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| GNU 15.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| GNU 16.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| GNU 16.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| GNU 16.1.0 | arm64 | Ubuntu 24.04 | GitHub |
|
| GNU 16.1.0 | arm64 | Ubuntu 24.04 | GitHub |
|
||||||
| icpc (ICC) 2021.10.0 20230609 | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
| icpc (ICC) 2021.10.0 20230609 | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
||||||
| icpx (Intel oneAPI DPC++/C++) 2025.3.2 | x86_64 | Ubuntu 24.04 LTS | GitHub |
|
| icpx (Intel oneAPI DPC++/C++) 2025.3.2 | x86_64 | Ubuntu 24.04 LTS | GitHub |
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ work items are tracked in the [GitHub milestones](https://github.com/nlohmann/js
|
|||||||
|
|
||||||
## What the project will not do
|
## What the project will not do
|
||||||
|
|
||||||
- **Break the public API of version 3.x.** See [API stability](#api-stability) for what this covers.
|
- **Break the public API of version 3.x.** See the
|
||||||
|
[contribution guidelines](https://github.com/nlohmann/json/blob/develop/.github/CONTRIBUTING.md#break-the-public-api)
|
||||||
|
for what counts as a breaking change.
|
||||||
- **Require a newer C++ standard than C++11.**
|
- **Require a newer C++ standard than C++11.**
|
||||||
- **Break JSON conformance** or enable non-standard extensions by default.
|
- **Break JSON conformance** or enable non-standard extensions by default.
|
||||||
- **Add dependencies** or require a build step. The library remains header-only, and the single header
|
- **Add dependencies** or require a build step. The library remains header-only, and the single header
|
||||||
@@ -33,32 +35,6 @@ work items are tracked in the [GitHub milestones](https://github.com/nlohmann/js
|
|||||||
- **Trade simplicity for speed or memory efficiency.** Performance improvements are welcome, but the library is not
|
- **Trade simplicity for speed or memory efficiency.** Performance improvements are welcome, but the library is not
|
||||||
meant to compete with the fastest JSON libraries, see [Design goals](../home/design_goals.md).
|
meant to compete with the fastest JSON libraries, see [Design goals](../home/design_goals.md).
|
||||||
|
|
||||||
## API stability
|
|
||||||
|
|
||||||
Releases follow [semantic versioning](https://semver.org): a minor or patch release of version 3.x does not break code
|
|
||||||
that uses the public API. In particular, a 3.x release does not:
|
|
||||||
|
|
||||||
- change the signature of a function (its parameter types, return type, number of parameters, or the const-ness of a
|
|
||||||
member function);
|
|
||||||
- remove or rename a function or class;
|
|
||||||
- change which exceptions a function throws, or the [exception ids](../home/exceptions.md);
|
|
||||||
- change access specifiers or default arguments.
|
|
||||||
|
|
||||||
Exceptions to these rules, for instance when fixing a bug requires changing the exception a function throws, are
|
|
||||||
documented in the [release notes](../home/releases.md).
|
|
||||||
|
|
||||||
The following are **not** part of the public API and may change in any release, including patch releases:
|
|
||||||
|
|
||||||
- The text of exception messages returned by `what()`. Use the [exception id](../home/exceptions.md) to tell errors
|
|
||||||
apart.
|
|
||||||
- The ABI, including `sizeof(basic_json)` and the memory layout of its values. Recompile your code when you upgrade the
|
|
||||||
library. The [versioned inline namespace](../features/namespace.md) turns mixing versions into a link error.
|
|
||||||
- Everything in namespace `nlohmann::detail`, and macros and type traits that are not documented in the
|
|
||||||
[API reference](../api/basic_json/index.md).
|
|
||||||
|
|
||||||
Changes that would break the public API are only added behind a macro whose default keeps the 3.x behavior, see
|
|
||||||
[Version 4.0](#version-40).
|
|
||||||
|
|
||||||
## Version 4.0
|
## Version 4.0
|
||||||
|
|
||||||
There is no release date for version 4.0 yet. Proposals that need a major version, for instance stricter type
|
There is no release date for version 4.0 yet. Proposals that need a major version, for instance stricter type
|
||||||
@@ -88,8 +64,6 @@ The following macros guard changes that are planned to become the default in ver
|
|||||||
| [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) | `0` | `1`: reading from a stream does not consume the character after a number | – | 3.13.0 |
|
| [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) | `0` | `1`: reading from a stream does not consume the character after a number | – | 3.13.0 |
|
||||||
| [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) | `0` | `1`: a NUL byte in the input is a parse error instead of the end of input | [`JSON_StrictNulHandling`](../integration/cmake.md#json_strictnulhandling) | 3.13.0 |
|
| [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) | `0` | `1`: a NUL byte in the input is a parse error instead of the end of input | [`JSON_StrictNulHandling`](../integration/cmake.md#json_strictnulhandling) | 3.13.0 |
|
||||||
| [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) | `0` | `1`: `to_cbor`, `to_ubjson`, `to_bjdata`, and `to_bson` throw for strings that are not valid UTF-8 by default | [`JSON_StrictBinaryUTF8`](../integration/cmake.md#json_strictbinaryutf8) | 3.13.0 |
|
| [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) | `0` | `1`: `to_cbor`, `to_ubjson`, `to_bjdata`, and `to_bson` throw for strings that are not valid UTF-8 by default | [`JSON_StrictBinaryUTF8`](../integration/cmake.md#json_strictbinaryutf8) | 3.13.0 |
|
||||||
| [`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md) | `0` | `1`: a `basic_json` value can no longer be created from a one-element tuple of a reference to it, such as `std::forward_as_tuple(j)`| [`JSON_DisableTupleReferenceConversion`](../integration/cmake.md#json_disabletuplereferenceconversion) | 3.13.0 |
|
|
||||||
| [`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md) | `0` | removed: the deprecated functions are removed (see below); the `from_*(ptr, len)` overloads stay deleted | [`JSON_DeleteDeprecatedFunctions`](../integration/cmake.md#json_deletedeprecatedfunctions) | 3.13.0 |
|
|
||||||
|
|
||||||
For example, the following makes a 3.x release behave like version 4.0 with respect to these changes:
|
For example, the following makes a 3.x release behave like version 4.0 with respect to these changes:
|
||||||
|
|
||||||
@@ -101,8 +75,6 @@ For example, the following makes a 3.x release behave like version 4.0 with resp
|
|||||||
#define JSON_PRECISE_STREAM_POSITION 1
|
#define JSON_PRECISE_STREAM_POSITION 1
|
||||||
#define JSON_STRICT_NUL_HANDLING 1
|
#define JSON_STRICT_NUL_HANDLING 1
|
||||||
#define JSON_STRICT_BINARY_UTF8 1
|
#define JSON_STRICT_BINARY_UTF8 1
|
||||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1
|
|
||||||
#define JSON_DELETE_DEPRECATED_FUNCTIONS 1
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -112,13 +84,8 @@ way to achieve this.
|
|||||||
### Removal of deprecated functions
|
### Removal of deprecated functions
|
||||||
|
|
||||||
Version 4.0 will remove all deprecated functions. Compiling with deprecation warnings enabled shows which of them your
|
Version 4.0 will remove all deprecated functions. Compiling with deprecation warnings enabled shows which of them your
|
||||||
code still uses. Defining [`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md) to
|
code still uses. The [migration guide](../integration/migration_guide.md#replace-deprecated-functions) shows how to
|
||||||
`1` turns these warnings into errors, as the deprecated functions are then deleted. The
|
replace each of them.
|
||||||
[migration guide](../integration/migration_guide.md#replace-deprecated-functions) shows how to replace each of them.
|
|
||||||
|
|
||||||
The `from_*` overloads taking a pointer and a length are not removed in version 4.0, but stay deleted. Without them, a
|
|
||||||
call like `from_cbor(ptr, len)` would still compile: it would read `ptr` as a NUL-terminated string and convert `len`
|
|
||||||
to the `strict` parameter.
|
|
||||||
|
|
||||||
| Deprecated | Since | Migration |
|
| Deprecated | Since | Migration |
|
||||||
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------------------------------------------------------------------------------|
|
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------------------------------------------------------------------------------|
|
||||||
@@ -130,7 +97,6 @@ to the `strict` parameter.
|
|||||||
| [`json_pointer::operator string_t`](../api/json_pointer/operator_string_t.md) | 3.11.0 | [JSON Pointers](../integration/migration_guide.md#json-pointers) |
|
| [`json_pointer::operator string_t`](../api/json_pointer/operator_string_t.md) | 3.11.0 | [JSON Pointers](../integration/migration_guide.md#json-pointers) |
|
||||||
| [`json_pointer`](../api/json_pointer/index.md) with a `basic_json` type as template argument, and the overloads of `value`, `contains`, `operator[]`, and `at` accepting such a pointer | 3.11.0 | [JSON Pointers](../integration/migration_guide.md#json-pointers) |
|
| [`json_pointer`](../api/json_pointer/index.md) with a `basic_json` type as template argument, and the overloads of `value`, `contains`, `operator[]`, and `at` accepting such a pointer | 3.11.0 | [JSON Pointers](../integration/migration_guide.md#json-pointers) |
|
||||||
| Comparing a [`json_pointer`](../api/json_pointer/index.md) with a string via [`operator==`](../api/json_pointer/operator_eq.md) or [`operator!=`](../api/json_pointer/operator_ne.md) | 3.11.2 | [JSON Pointers](../integration/migration_guide.md#json-pointers) |
|
| Comparing a [`json_pointer`](../api/json_pointer/index.md) with a string via [`operator==`](../api/json_pointer/operator_eq.md) or [`operator!=`](../api/json_pointer/operator_ne.md) | 3.11.2 | [JSON Pointers](../integration/migration_guide.md#json-pointers) |
|
||||||
| [`from_bjdata`](../api/basic_json/from_bjdata.md) and [`from_bon8`](../api/basic_json/from_bon8.md) with `(ptr, len)` | 3.13.0 | [Parsing](../integration/migration_guide.md#parsing) |
|
|
||||||
|
|
||||||
The deprecated legacy comparison of discarded values is controlled by a macro and therefore listed in the table above.
|
The deprecated legacy comparison of discarded values is controlled by a macro and therefore listed in the table above.
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,19 @@ class visitor_adaptor_with_metadata
|
|||||||
void do_visit(const Ptr& ptr, const Fnc& fnc) const;
|
void do_visit(const Ptr& ptr, const Fnc& fnc) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
using json = nlohmann::json::with_base_class_t<visitor_adaptor_with_metadata>;
|
using json = nlohmann::basic_json <
|
||||||
|
std::map,
|
||||||
|
std::vector,
|
||||||
|
std::string,
|
||||||
|
bool,
|
||||||
|
std::int64_t,
|
||||||
|
std::uint64_t,
|
||||||
|
double,
|
||||||
|
std::allocator,
|
||||||
|
nlohmann::adl_serializer,
|
||||||
|
std::vector<std::uint8_t>,
|
||||||
|
visitor_adaptor_with_metadata
|
||||||
|
>;
|
||||||
|
|
||||||
template <class Fnc>
|
template <class Fnc>
|
||||||
void visitor_adaptor_with_metadata::visit(const Fnc& fnc) const
|
void visitor_adaptor_with_metadata::visit(const Fnc& fnc) const
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ int main()
|
|||||||
<< "hash(false) = " << std::hash<json> {}(json(false)) << '\n'
|
<< "hash(false) = " << std::hash<json> {}(json(false)) << '\n'
|
||||||
<< "hash(0) = " << std::hash<json> {}(json(0)) << '\n'
|
<< "hash(0) = " << std::hash<json> {}(json(0)) << '\n'
|
||||||
<< "hash(0U) = " << std::hash<json> {}(json(0U)) << '\n'
|
<< "hash(0U) = " << std::hash<json> {}(json(0U)) << '\n'
|
||||||
<< "hash(0.0) = " << std::hash<json> {}(json(0.0)) << '\n'
|
|
||||||
<< "hash(\"\") = " << std::hash<json> {}(json("")) << '\n'
|
<< "hash(\"\") = " << std::hash<json> {}(json("")) << '\n'
|
||||||
<< "hash({}) = " << std::hash<json> {}(json::object()) << '\n'
|
<< "hash({}) = " << std::hash<json> {}(json::object()) << '\n'
|
||||||
<< "hash([]) = " << std::hash<json> {}(json::array()) << '\n'
|
<< "hash([]) = " << std::hash<json> {}(json::array()) << '\n'
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
hash(null) = 2654435769
|
hash(null) = 2654435769
|
||||||
hash(false) = 2654436030
|
hash(false) = 2654436030
|
||||||
hash(0) = 2654436221
|
hash(0) = 2654436095
|
||||||
hash(0U) = 2654436221
|
hash(0U) = 2654436156
|
||||||
hash(0.0) = 2654436221
|
hash("") = 6142509191626859748
|
||||||
hash("") = 11160318156688833227
|
|
||||||
hash({}) = 2654435832
|
hash({}) = 2654435832
|
||||||
hash([]) = 2654435899
|
hash([]) = 2654435899
|
||||||
hash({"hello": "world"}) = 3701319991624763853
|
hash({"hello": "world"}) = 4469488738203676328
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#include <iostream>
|
|
||||||
#include <map>
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
// a JSON type that stores objects in a std::map (which keeps keys sorted)
|
|
||||||
// instead of the default ordered associative container
|
|
||||||
using sorted_json = nlohmann::json::with_object_t<std::map>;
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
sorted_json j;
|
|
||||||
j["c"] = 1;
|
|
||||||
j["a"] = 2;
|
|
||||||
j["b"] = 3;
|
|
||||||
|
|
||||||
// keys are sorted, because std::map is used to store the object
|
|
||||||
std::cout << j.dump() << std::endl;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"a":2,"b":3,"c":1}
|
|
||||||
@@ -141,14 +141,8 @@ The library uses the following mapping from JSON values types to BJData types ac
|
|||||||
parsed back as a regular array,
|
parsed back as a regular array,
|
||||||
- every entry of `"_ArraySize_"` is a positive integer, and their product is representable as a `std::size_t`,
|
- 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
|
- `"_ArrayData_"` is an array holding exactly that many elements, and
|
||||||
- every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"`: for the integer types, a
|
- every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"` (a floating-point number for
|
||||||
value that fits the named width; for `double`, any value; for `single`, a value that survives narrowing to
|
`single` and `double`, an integer otherwise).
|
||||||
`float` and back without change (for instance, `0.1` does not, since it is not exactly representable as
|
|
||||||
`float`).
|
|
||||||
|
|
||||||
An annotated object is always read back with its keys in the order shown above, `"_ArrayType_"`, `"_ArraySize_"`,
|
|
||||||
`"_ArrayData_"`, regardless of the order the ND-array's header stores them in on the wire. This matters for
|
|
||||||
`ordered_json`, whose comparison takes key order into account.
|
|
||||||
|
|
||||||
The current version of this library does not yet support automatic detection of and conversion from a nested JSON
|
The current version of this library does not yet support automatic detection of and conversion from a nested JSON
|
||||||
array input to a BJData ND-array.
|
array input to a BJData ND-array.
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ The library maps CBOR types to JSON value types as follows:
|
|||||||
| Byte string | binary | 0x59 |
|
| Byte string | binary | 0x59 |
|
||||||
| Byte string | binary | 0x5A |
|
| Byte string | binary | 0x5A |
|
||||||
| Byte string | binary | 0x5B |
|
| Byte string | binary | 0x5B |
|
||||||
| Byte string | binary | 0x5F |
|
|
||||||
| UTF-8 string | string | 0x60..0x77 |
|
| UTF-8 string | string | 0x60..0x77 |
|
||||||
| UTF-8 string | string | 0x78 |
|
| UTF-8 string | string | 0x78 |
|
||||||
| UTF-8 string | string | 0x79 |
|
| UTF-8 string | string | 0x79 |
|
||||||
@@ -157,9 +156,6 @@ The library maps CBOR types to JSON value types as follows:
|
|||||||
| Single-Precision Float | number_float | 0xFA |
|
| Single-Precision Float | number_float | 0xFA |
|
||||||
| Double-Precision Float | number_float | 0xFB |
|
| Double-Precision Float | number_float | 0xFB |
|
||||||
|
|
||||||
Indefinite-length UTF-8 strings (0x7F) and byte strings (0x5F) are supported. Each chunk must be a definite-length
|
|
||||||
string of the same major type, as required by [RFC 8949, Section 3.2.3](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.2.3).
|
|
||||||
|
|
||||||
!!! warning "Incomplete mapping"
|
!!! warning "Incomplete mapping"
|
||||||
|
|
||||||
The mapping is **incomplete** in the sense that not all CBOR types can be converted to a JSON value. The following CBOR types are not supported and will yield parse errors:
|
The mapping is **incomplete** in the sense that not all CBOR types can be converted to a JSON value. The following CBOR types are not supported and will yield parse errors:
|
||||||
@@ -172,9 +168,9 @@ string of the same major type, as required by [RFC 8949, Section 3.2.3](https://
|
|||||||
!!! warning "Negative integer overflow"
|
!!! warning "Negative integer overflow"
|
||||||
|
|
||||||
CBOR negative integers (major type 1) are decoded as `-1 - n`. If the encoded magnitude `n` is too large for the
|
CBOR negative integers (major type 1) are decoded as `-1 - n`. If the encoded magnitude `n` is too large for the
|
||||||
result to fit into `number_integer_t` (`std::int64_t` by default), the result is stored as `number_float_t`, like
|
result to fit into `number_integer_t` (`std::int64_t` by default), parsing fails with a
|
||||||
a too small integer in JSON text. For example, `-18446744073709551616` (`0x3B` followed by eight `0xFF` bytes) is
|
[`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) exception rather than overflowing
|
||||||
stored as `-1.8446744073709552e+19`.
|
silently.
|
||||||
|
|
||||||
!!! warning "Object keys"
|
!!! warning "Object keys"
|
||||||
|
|
||||||
|
|||||||
@@ -58,23 +58,6 @@ assert(jPi.get<TaskState>() == TS_INVALID );
|
|||||||
--8<-- "examples/nlohmann_json_serialize_enum.output"
|
--8<-- "examples/nlohmann_json_serialize_enum.output"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Maps with enum keys
|
|
||||||
|
|
||||||
By default, maps with enum keys, such as `std::map<TaskState, std::string>`, are stored as arrays of `[key, value]`
|
|
||||||
pairs, because JSON object keys must be strings. Define
|
|
||||||
[`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md) before including the
|
|
||||||
library to store them as objects, with the keys converted by the enum's `to_json()` function:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
std::map<TaskState, std::string> m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
|
|
||||||
|
|
||||||
json j = m;
|
|
||||||
// default: [["stopped","aa"],["completed","bb"]]
|
|
||||||
// with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS: {"completed":"bb","stopped":"aa"}
|
|
||||||
```
|
|
||||||
|
|
||||||
Either form can be read back, with or without the macro.
|
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
Just as in [Arbitrary Type Conversions](arbitrary_types.md) above,
|
Just as in [Arbitrary Type Conversions](arbitrary_types.md) above,
|
||||||
|
|||||||
@@ -23,17 +23,6 @@ This macro overrides [`#!cpp catch`](https://en.cppreference.com/w/cpp/language/
|
|||||||
|
|
||||||
See [full documentation of `JSON_CATCH_USER(exception)`](../api/macros/json_throw_user.md).
|
See [full documentation of `JSON_CATCH_USER(exception)`](../api/macros/json_throw_user.md).
|
||||||
|
|
||||||
## `JSON_DELETE_DEPRECATED_FUNCTIONS`
|
|
||||||
|
|
||||||
When defined to `1`, all deprecated functions are declared as deleted instead of only being marked as deprecated, so
|
|
||||||
code that still calls them no longer compiles. This way, you can find all calls that need to be replaced before version
|
|
||||||
4.0.0 removes these functions.
|
|
||||||
|
|
||||||
The macro can also be set with the CMake option
|
|
||||||
[`JSON_DeleteDeprecatedFunctions`](../integration/cmake.md#json_deletedeprecatedfunctions) (`OFF` by default).
|
|
||||||
|
|
||||||
See [full documentation of `JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md).
|
|
||||||
|
|
||||||
## `JSON_DIAGNOSTICS`
|
## `JSON_DIAGNOSTICS`
|
||||||
|
|
||||||
This macro enables extended diagnostics for exception messages. Possible values are `1` to enable or `0` to disable
|
This macro enables extended diagnostics for exception messages. Possible values are `1` to enable or `0` to disable
|
||||||
@@ -97,8 +86,7 @@ See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json
|
|||||||
## `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`
|
## `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`
|
||||||
|
|
||||||
When defined to `1`, a JSON value can no longer be created from a one-element `std::tuple` holding a reference to a JSON
|
When defined to `1`, a JSON value can no longer be created from a one-element `std::tuple` holding a reference to a JSON
|
||||||
value, such as the result of `std::forward_as_tuple(j)`. This lets `std::tuple` convert such tuples element-wise. This
|
value, such as the result of `std::forward_as_tuple(j)`. This lets `std::tuple` convert such tuples element-wise.
|
||||||
is planned to become the default in version 4.0.0.
|
|
||||||
|
|
||||||
See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md).
|
See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md).
|
||||||
|
|
||||||
@@ -210,13 +198,6 @@ default.
|
|||||||
|
|
||||||
See [full documentation of `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md).
|
See [full documentation of `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md).
|
||||||
|
|
||||||
## `JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`
|
|
||||||
|
|
||||||
When defined to `1`, maps with enum keys (e.g., `std::map<E, T>`) are stored as objects, using the enum's conversion for
|
|
||||||
the keys, instead of arrays of `[key, value]` pairs. It is switched off (`0`) by default.
|
|
||||||
|
|
||||||
See [full documentation of `JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md).
|
|
||||||
|
|
||||||
## `JSON_USE_SIMDUTF`
|
## `JSON_USE_SIMDUTF`
|
||||||
|
|
||||||
When defined, UTF-8 validation of JSON strings read from contiguous byte input is delegated to the
|
When defined, UTF-8 validation of JSON strings read from contiguous byte input is delegated to the
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ The complete default namespace name is derived as follows:
|
|||||||
- [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) defined non-zero appends `_psp`.
|
- [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) defined non-zero appends `_psp`.
|
||||||
- [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) defined non-zero appends `_snul`.
|
- [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) defined non-zero appends `_snul`.
|
||||||
- [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) defined non-zero appends `_sbu8`.
|
- [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) defined non-zero appends `_sbu8`.
|
||||||
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md) defined non-zero
|
|
||||||
appends `_ekmo`.
|
|
||||||
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by
|
- 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)
|
underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component)
|
||||||
below.
|
below.
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ always use the scalar path regardless of this macro.
|
|||||||
|
|
||||||
Parsing always produces SAX events internally; [`parse`](../api/basic_json/parse.md) simply feeds them to a consumer
|
Parsing always produces SAX events internally; [`parse`](../api/basic_json/parse.md) simply feeds them to a consumer
|
||||||
that builds a complete `basic_json` value tree (a DOM) in memory. For documents too large to comfortably hold as
|
that builds a complete `basic_json` value tree (a DOM) in memory. For documents too large to comfortably hold as
|
||||||
a DOM, three alternatives avoid building it:
|
a DOM, two alternatives avoid building it:
|
||||||
|
|
||||||
- Implement the [SAX interface](parsing/sax_interface.md) directly and pass it to
|
- Implement the [SAX interface](parsing/sax_interface.md) directly and pass it to
|
||||||
[`sax_parse`](../api/basic_json/sax_parse.md); only the parts of the input you choose to keep ever become
|
[`sax_parse`](../api/basic_json/sax_parse.md); only the parts of the input you choose to keep ever become
|
||||||
@@ -64,12 +64,6 @@ a DOM, three alternatives avoid building it:
|
|||||||
discard finished elements as soon as they are handled, so memory usage stays bounded by one element (plus the
|
discard finished elements as soon as they are handled, so memory usage stays bounded by one element (plus the
|
||||||
unparsed remainder of the input) instead of the whole document -- see the [recipe for streaming a large homogeneous
|
unparsed remainder of the input) instead of the whole document -- see the [recipe for streaming a large homogeneous
|
||||||
array](parsing/parser_callbacks.md#recipe-streaming-a-large-homogeneous-array).
|
array](parsing/parser_callbacks.md#recipe-streaming-a-large-homogeneous-array).
|
||||||
- Parse into a [`json_document`](json_view.md) (`#!cpp <nlohmann/json_view.hpp>`) instead of a `basic_json`. It keeps
|
|
||||||
the input text and builds a flat index of 16 bytes per value; strings and numbers are not copied, but read from the
|
|
||||||
text when needed. Read-only [views](../api/basic_json_view/index.md) give the familiar element access, and only the
|
|
||||||
parts you [`materialize()`](../api/basic_json_view/materialize.md) become `basic_json` values. A document that
|
|
||||||
borrows the text instead of owning a copy needs the text to outlive it; see
|
|
||||||
[choosing between `json`, the SAX interface, and `json_view`](json_view.md#choosing-between-json-ordered_json-the-sax-interface-and-json_view).
|
|
||||||
|
|
||||||
If the data is naturally record-oriented, consider [JSON Lines](parsing/json_lines.md) instead of one large JSON
|
If the data is naturally record-oriented, consider [JSON Lines](parsing/json_lines.md) instead of one large JSON
|
||||||
document: reading and parsing it line by line with `#!cpp std::getline` means only one line's value is ever in memory
|
document: reading and parsing it line by line with `#!cpp std::getline` means only one line's value is ever in memory
|
||||||
@@ -215,7 +209,6 @@ those headers are then never processed by the compiler at all.
|
|||||||
- [Architecture](../home/architecture.md) - how input adapters, the lexer, and the serializer fit together
|
- [Architecture](../home/architecture.md) - how input adapters, the lexer, and the serializer fit together
|
||||||
- [Parsing](parsing/index.md) - the available parsing functions and inputs
|
- [Parsing](parsing/index.md) - the available parsing functions and inputs
|
||||||
- [SAX interface](parsing/sax_interface.md) - parse without building a DOM
|
- [SAX interface](parsing/sax_interface.md) - parse without building a DOM
|
||||||
- [Zero-copy JSON views](json_view.md) - parse into a flat index of the text and read it without building a DOM
|
|
||||||
- [Binary formats](binary_formats/index.md) - compact alternatives to JSON text
|
- [Binary formats](binary_formats/index.md) - compact alternatives to JSON text
|
||||||
- [Object Order](object_order.md) - `json` vs. `ordered_json` and other `ObjectType` choices
|
- [Object Order](object_order.md) - `json` vs. `ordered_json` and other `ObjectType` choices
|
||||||
- [Template Parameter Requirements](types/template_parameters.md) - custom container and allocator types
|
- [Template Parameter Requirements](types/template_parameters.md) - custom container and allocator types
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ serialization fails by default. The fourth argument of `dump` selects an
|
|||||||
- `strict` (default) — throw a [`type_error.316`](../home/exceptions.md#jsonexceptiontype_error316) exception.
|
- `strict` (default) — throw a [`type_error.316`](../home/exceptions.md#jsonexceptiontype_error316) exception.
|
||||||
- `replace` — replace invalid bytes with the Unicode replacement character U+FFFD (`�`).
|
- `replace` — replace invalid bytes with the Unicode replacement character U+FFFD (`�`).
|
||||||
- `ignore` — silently drop invalid bytes.
|
- `ignore` — silently drop invalid bytes.
|
||||||
- `keep` — copy invalid bytes to the output unchanged; the result is not valid UTF-8.
|
|
||||||
|
|
||||||
??? example "Example: serialize invalid UTF-8 with different error handlers"
|
??? example "Example: serialize invalid UTF-8 with different error handlers"
|
||||||
|
|
||||||
|
|||||||
@@ -331,6 +331,9 @@ An unexpected byte was read in a [binary format](../features/binary_formats/inde
|
|||||||
[json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON binary: byte array length cannot be negative, is -1
|
[json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON binary: byte array length cannot be negative, is -1
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
|
[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow
|
||||||
|
```
|
||||||
|
```
|
||||||
[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5)
|
[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -613,9 +616,6 @@ During implicit or explicit value conversion, the JSON type must be compatible w
|
|||||||
[json.exception.type_error.302] type must be string, but is object
|
[json.exception.type_error.302] type must be string, but is object
|
||||||
```
|
```
|
||||||
|
|
||||||
This exception is also thrown with [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)
|
|
||||||
if a key of a map with enum keys is not converted to a string, for instance, because the enum is stored as an integer.
|
|
||||||
|
|
||||||
### json.exception.type_error.303
|
### json.exception.type_error.303
|
||||||
|
|
||||||
To retrieve a reference to a value stored in a `basic_json` object with `get_ref`, the type of the reference must match the value type. For instance, for a JSON array, the `ReferenceType` must be `array_t &`.
|
To retrieve a reference to a value stored in a `basic_json` object with `get_ref`, the type of the reference must match the value type. For instance, for a JSON array, the `ReferenceType` must be `array_t &`.
|
||||||
@@ -788,7 +788,6 @@ as well for a string value or object key that is not valid UTF-8 if their `error
|
|||||||
- Pass an error handler as last parameter to the `dump()` function to avoid this exception:
|
- Pass an error handler as last parameter to the `dump()` function to avoid this exception:
|
||||||
- `json::error_handler_t::replace` will replace invalid bytes sequences with `U+FFFD`
|
- `json::error_handler_t::replace` will replace invalid bytes sequences with `U+FFFD`
|
||||||
- `json::error_handler_t::ignore` will silently ignore invalid byte sequences
|
- `json::error_handler_t::ignore` will silently ignore invalid byte sequences
|
||||||
- `json::error_handler_t::keep` will copy invalid byte sequences to the output unchanged
|
|
||||||
|
|
||||||
### json.exception.type_error.317
|
### json.exception.type_error.317
|
||||||
|
|
||||||
@@ -809,19 +808,6 @@ The dynamic type of the object cannot be represented in the requested serializat
|
|||||||
|
|
||||||
Encapsulate the JSON value in an object. That is, instead of serializing `#!json true`, serialize `#!json {"value": true}`
|
Encapsulate the JSON value in an object. That is, instead of serializing `#!json true`, serialize `#!json {"value": true}`
|
||||||
|
|
||||||
### json.exception.type_error.318
|
|
||||||
|
|
||||||
With [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md), a map with enum
|
|
||||||
keys is stored as an object. This exception is thrown if two of its keys are converted to the same string, so one of the
|
|
||||||
entries would be lost. This happens, for instance, if [`NLOHMANN_JSON_SERIALIZE_ENUM`](../api/macros/nlohmann_json_serialize_enum.md)
|
|
||||||
does not list an enumerator and it is therefore converted like the first listed one.
|
|
||||||
|
|
||||||
!!! failure "Example message"
|
|
||||||
|
|
||||||
```
|
|
||||||
[json.exception.type_error.318] duplicate object key 'red'
|
|
||||||
```
|
|
||||||
|
|
||||||
### json.exception.type_error.319
|
### json.exception.type_error.319
|
||||||
|
|
||||||
[`basic_json_document::set`](../api/basic_json_document/set.md) and
|
[`basic_json_document::set`](../api/basic_json_document/set.md) and
|
||||||
@@ -860,20 +846,6 @@ exception on a big-endian target, since the image format is little-endian only.
|
|||||||
|
|
||||||
This exception was added in version 3.13.0, together with [images](../features/json_view.md#images).
|
This exception was added in version 3.13.0, together with [images](../features/json_view.md#images).
|
||||||
|
|
||||||
### json.exception.type_error.321
|
|
||||||
|
|
||||||
A discarded value (one created by [`parse()`](../api/basic_json/parse.md) with a callback that returns `false` for the
|
|
||||||
value, or by default-constructing a [`basic_json`](../api/basic_json/index.md) with
|
|
||||||
[`value_t::discarded`](../api/basic_json/value_t.md)) was passed to a binary serialization function, either directly or
|
|
||||||
nested in an array or object. There is no way to represent a discarded value in CBOR, MessagePack, UBJSON, BJData, or BSON.
|
|
||||||
|
|
||||||
!!! failure "Example message"
|
|
||||||
|
|
||||||
Serializing `#!json [1, 2]` to CBOR, where the second element was discarded by a parser callback:
|
|
||||||
```
|
|
||||||
[json.exception.type_error.321] cannot serialize discarded value to CBOR
|
|
||||||
```
|
|
||||||
|
|
||||||
## Out of range
|
## Out of range
|
||||||
|
|
||||||
This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys.
|
This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys.
|
||||||
@@ -946,18 +918,13 @@ The JSON Patch operations 'remove' and 'add' cannot be applied to the root eleme
|
|||||||
|
|
||||||
### json.exception.out_of_range.406
|
### json.exception.out_of_range.406
|
||||||
|
|
||||||
A parsed number could not be stored without changing it to NaN or INF. For the binary formats, this happens when a
|
A parsed number could not be stored as without changing it to NaN or INF.
|
||||||
finite floating-point number does not fit into [`number_float_t`](../api/basic_json/number_float_t.md), for example a
|
|
||||||
double-precision number when `number_float_t` is `#!cpp float`.
|
|
||||||
|
|
||||||
!!! failure "Example messages"
|
!!! failure "Example message"
|
||||||
|
|
||||||
```
|
```
|
||||||
number overflow parsing '10E1000'
|
number overflow parsing '10E1000'
|
||||||
```
|
```
|
||||||
```
|
|
||||||
[json.exception.out_of_range.406] syntax error while parsing CBOR value: number overflow
|
|
||||||
```
|
|
||||||
|
|
||||||
### json.exception.out_of_range.407
|
### json.exception.out_of_range.407
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ The library supports **Unicode input** as follows:
|
|||||||
- The library will not replace [Unicode noncharacters](http://www.unicode.org/faq/private_use.html#nonchar1).
|
- The library will not replace [Unicode noncharacters](http://www.unicode.org/faq/private_use.html#nonchar1).
|
||||||
- Invalid surrogates (e.g., incomplete pairs such as `\uDEAD`) will yield parse errors.
|
- Invalid surrogates (e.g., incomplete pairs such as `\uDEAD`) will yield parse errors.
|
||||||
- The strings stored in the library are UTF-8 encoded. When using the default string type (`std::string`), note that its length/size functions return the number of stored bytes rather than the number of characters or glyphs.
|
- The strings stored in the library are UTF-8 encoded. When using the default string type (`std::string`), note that its length/size functions return the number of stored bytes rather than the number of characters or glyphs.
|
||||||
- When you store strings with different encodings in the library, calling [`dump()`](../api/basic_json/dump.md) may throw an exception unless `json::error_handler_t::replace`, `json::error_handler_t::ignore`, or `json::error_handler_t::keep` are used as error handlers.
|
- When you store strings with different encodings in the library, calling [`dump()`](../api/basic_json/dump.md) may throw an exception unless `json::error_handler_t::replace` or `json::error_handler_t::ignore` are used as error handlers.
|
||||||
|
|
||||||
In most cases, the parser is right to complain, because the input is not UTF-8 encoded. This is especially true for Microsoft Windows, where Latin-1 or ISO 8859-1 is often the standard encoding.
|
In most cases, the parser is right to complain, because the input is not UTF-8 encoded. This is especially true for Microsoft Windows, where Latin-1 or ISO 8859-1 is often the standard encoding.
|
||||||
|
|
||||||
|
|||||||
@@ -126,18 +126,12 @@ automatically download a release as a dependency at configure time.
|
|||||||
|
|
||||||
### `JSON_BuildTests`
|
### `JSON_BuildTests`
|
||||||
|
|
||||||
Build the unit tests when [`BUILD_TESTING`](https://cmake.org/cmake/help/latest/command/enable_testing.html) is enabled. This option is `ON` by default if the library's CMake project is the top project and the `tests` directory exists (the release archive `json.tar.xz` does not contain it). That is, when integrating the library as described above, the test suite is not built unless explicitly switched on with this option.
|
Build the unit tests when [`BUILD_TESTING`](https://cmake.org/cmake/help/latest/command/enable_testing.html) is enabled. This option is `ON` by default if the library's CMake project is the top project. That is, when integrating the library as described above, the test suite is not built unless explicitly switched on with this option.
|
||||||
|
|
||||||
### `JSON_CI`
|
### `JSON_CI`
|
||||||
|
|
||||||
Enable CI build targets. The exact targets are used during the several CI steps and are subject to change without notice. This option is `OFF` by default.
|
Enable CI build targets. The exact targets are used during the several CI steps and are subject to change without notice. This option is `OFF` by default.
|
||||||
|
|
||||||
### `JSON_DeleteDeprecatedFunctions`
|
|
||||||
|
|
||||||
Delete the deprecated functions instead of only deprecating them by defining the macro
|
|
||||||
[`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md). This option is `OFF` by
|
|
||||||
default.
|
|
||||||
|
|
||||||
### `JSON_Diagnostics`
|
### `JSON_Diagnostics`
|
||||||
|
|
||||||
Enable [extended diagnostic messages](../home/exceptions.md#extended-diagnostic-messages) by defining macro [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md). This option is `OFF` by default.
|
Enable [extended diagnostic messages](../home/exceptions.md#extended-diagnostic-messages) by defining macro [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md). This option is `OFF` by default.
|
||||||
|
|||||||
@@ -14,13 +14,6 @@ deprecations are annotated with
|
|||||||
[`HEDLEY_DEPRECATED_FOR`](https://nemequ.github.io/hedley/api-reference.html#HEDLEY_DEPRECATED_FOR) to report which
|
[`HEDLEY_DEPRECATED_FOR`](https://nemequ.github.io/hedley/api-reference.html#HEDLEY_DEPRECATED_FOR) to report which
|
||||||
function to use instead.
|
function to use instead.
|
||||||
|
|
||||||
!!! tip "Find all calls of deprecated functions"
|
|
||||||
|
|
||||||
Define [`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md) to `1` (or set the
|
|
||||||
CMake option [`JSON_DeleteDeprecatedFunctions`](cmake.md#json_deletedeprecatedfunctions)) to delete all deprecated
|
|
||||||
functions. Every remaining call then fails to compile, even if deprecation warnings are disabled, so your code is
|
|
||||||
ready for version 4.0.0 once it compiles with the macro.
|
|
||||||
|
|
||||||
### Parsing
|
### Parsing
|
||||||
|
|
||||||
- Function `friend std::istream& operator<<(basic_json&, std::istream&)` is deprecated since 3.0.0. Please use
|
- Function `friend std::istream& operator<<(basic_json&, std::istream&)` is deprecated since 3.0.0. Please use
|
||||||
@@ -48,10 +41,8 @@ function to use instead.
|
|||||||
[`from_ubjson`](../api/basic_json/from_ubjson.md), and [`from_bson`](../api/basic_json/from_bson.md)) via initializer
|
[`from_ubjson`](../api/basic_json/from_ubjson.md), and [`from_bson`](../api/basic_json/from_bson.md)) via initializer
|
||||||
lists is deprecated since 3.8.0. Instead, pass two iterators; for instance, call `from_cbor(ptr, ptr+len)` instead of
|
lists is deprecated since 3.8.0. Instead, pass two iterators; for instance, call `from_cbor(ptr, ptr+len)` instead of
|
||||||
`from_cbor({ptr, len})`. Likewise, passing a pointer and a length as two separate arguments to `from_cbor`,
|
`from_cbor({ptr, len})`. Likewise, passing a pointer and a length as two separate arguments to `from_cbor`,
|
||||||
`from_msgpack`, `from_ubjson`, and `from_bson` is deprecated since 3.8.0, and to
|
`from_msgpack`, `from_ubjson`, and `from_bson` is deprecated since 3.8.0; call `from_cbor(ptr, ptr+len)` instead of
|
||||||
[`from_bjdata`](../api/basic_json/from_bjdata.md) and [`from_bon8`](../api/basic_json/from_bon8.md) since 3.13.0; call
|
`from_cbor(ptr, len)`.
|
||||||
`from_cbor(ptr, ptr+len)` instead of `from_cbor(ptr, len)`. These overloads will not be removed in version 4.0.0, but
|
|
||||||
deleted, so a call like `from_cbor(ptr, len)` cannot compile and convert `len` to the `strict` parameter.
|
|
||||||
|
|
||||||
=== "Deprecated"
|
=== "Deprecated"
|
||||||
|
|
||||||
|
|||||||
@@ -233,7 +233,6 @@ nav:
|
|||||||
- 'update': api/basic_json/update.md
|
- 'update': api/basic_json/update.md
|
||||||
- 'value': api/basic_json/value.md
|
- 'value': api/basic_json/value.md
|
||||||
- 'value_t': api/basic_json/value_t.md
|
- 'value_t': api/basic_json/value_t.md
|
||||||
- 'with_t': api/basic_json/with_t.md
|
|
||||||
- basic_json_document:
|
- basic_json_document:
|
||||||
- 'Overview': api/basic_json_document/index.md
|
- 'Overview': api/basic_json_document/index.md
|
||||||
- '(Constructor)': api/basic_json_document/basic_json_document.md
|
- '(Constructor)': api/basic_json_document/basic_json_document.md
|
||||||
@@ -367,7 +366,6 @@ nav:
|
|||||||
- 'JSON_BRACE_INIT_COPY_SEMANTICS': api/macros/json_brace_init_copy_semantics.md
|
- 'JSON_BRACE_INIT_COPY_SEMANTICS': api/macros/json_brace_init_copy_semantics.md
|
||||||
- 'JSON_CATCH_USER, JSON_THROW_USER, JSON_TRY_USER': api/macros/json_throw_user.md
|
- 'JSON_CATCH_USER, JSON_THROW_USER, JSON_TRY_USER': api/macros/json_throw_user.md
|
||||||
- 'JSON_DIAGNOSTICS': api/macros/json_diagnostics.md
|
- 'JSON_DIAGNOSTICS': api/macros/json_diagnostics.md
|
||||||
- 'JSON_DELETE_DEPRECATED_FUNCTIONS': api/macros/json_delete_deprecated_functions.md
|
|
||||||
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
|
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
|
||||||
- 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md
|
- 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md
|
||||||
- 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md
|
- 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md
|
||||||
@@ -389,7 +387,6 @@ nav:
|
|||||||
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
|
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
|
||||||
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
|
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
|
||||||
- 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md
|
- 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md
|
||||||
- 'JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS': api/macros/json_use_objects_for_enum_keyed_maps.md
|
|
||||||
- 'JSON_USE_SIMDUTF': api/macros/json_use_simdutf.md
|
- 'JSON_USE_SIMDUTF': api/macros/json_use_simdutf.md
|
||||||
- 'JSON_VIEW_NO_SIMD': api/macros/json_view_no_simd.md
|
- 'JSON_VIEW_NO_SIMD': api/macros/json_view_no_simd.md
|
||||||
- 'JSON_VIEW_USE_SSSE3': api/macros/json_view_use_ssse3.md
|
- 'JSON_VIEW_USE_SSSE3': api/macros/json_view_use_ssse3.md
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ mkdocs-material==9.7.7 # theme for mkdocs
|
|||||||
mkdocs-material-extensions==1.3.1 # extensions
|
mkdocs-material-extensions==1.3.1 # extensions
|
||||||
mkdocs-minify-plugin==0.8.0 # plugin "minify"
|
mkdocs-minify-plugin==0.8.0 # plugin "minify"
|
||||||
mkdocs-redirects==1.2.3 # plugin "redirects"
|
mkdocs-redirects==1.2.3 # plugin "redirects"
|
||||||
mkdocs-htmlproofer-plugin==1.6.0 # plugin "htmlproofer"
|
mkdocs-htmlproofer-plugin==1.5.0 # plugin "htmlproofer"
|
||||||
mkdocs-llmstxt==0.5.0 # plugin "llmstxt"
|
mkdocs-llmstxt==0.5.0 # plugin "llmstxt"
|
||||||
|
|
||||||
PyYAML==6.0.3 # linter
|
PyYAML==6.0.3 # linter
|
||||||
+3
-3
@@ -1454,9 +1454,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
|||||||
@@ -50,10 +50,6 @@
|
|||||||
#define JSON_STRICT_BINARY_UTF8 0
|
#define JSON_STRICT_BINARY_UTF8 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||||
#else
|
#else
|
||||||
@@ -96,20 +92,14 @@
|
|||||||
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
|
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS _ekmo
|
|
||||||
#else
|
|
||||||
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||||
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Construct the namespace ABI tags component
|
// Construct the namespace ABI tags component
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h) json_abi ## a ## b ## c ## d ## e ## f ## g ## h
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) json_abi ## a ## b ## c ## d ## e ## f ## g
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h) \
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g) \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h)
|
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g)
|
||||||
|
|
||||||
#define NLOHMANN_JSON_ABI_TAGS \
|
#define NLOHMANN_JSON_ABI_TAGS \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||||
@@ -119,8 +109,7 @@
|
|||||||
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
|
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
|
||||||
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
||||||
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
|
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
|
||||||
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
|
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8)
|
||||||
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS)
|
|
||||||
|
|
||||||
// Construct the namespace version component
|
// Construct the namespace version component
|
||||||
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e)
|
|||||||
typename BasicJsonType::number_unsigned_t, underlying_type>::type;
|
typename BasicJsonType::number_unsigned_t, underlying_type>::type;
|
||||||
value_type val;
|
value_type val;
|
||||||
get_arithmetic_value(j, val);
|
get_arithmetic_value(j, val);
|
||||||
e = static_cast<EnumType>(bool_aware_static_cast<underlying_type>(val));
|
e = static_cast<EnumType>(static_cast<underlying_type>(val));
|
||||||
}
|
}
|
||||||
#endif // JSON_DISABLE_ENUM_SERIALIZATION
|
#endif // JSON_DISABLE_ENUM_SERIALIZATION
|
||||||
|
|
||||||
@@ -530,40 +530,11 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// read a map with enum keys from an object, using the enum's own from_json for
|
|
||||||
// the keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); this is the form written
|
|
||||||
// with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
template<typename BasicJsonType, typename Map>
|
|
||||||
inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::true_type /*key is enum*/)
|
|
||||||
{
|
|
||||||
if (!j.is_object())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
m.clear();
|
|
||||||
for (const auto& p : *j.template get_ptr<const typename BasicJsonType::object_t*>())
|
|
||||||
{
|
|
||||||
m.emplace(BasicJsonType(p.first).template get<typename Map::key_type>(), p.second.template get<typename Map::mapped_type>());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename BasicJsonType, typename Map>
|
|
||||||
inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, std::false_type /*key is enum*/)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator,
|
template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator,
|
||||||
typename = enable_if_t < !std::is_constructible <
|
typename = enable_if_t < !std::is_constructible <
|
||||||
typename BasicJsonType::string_t, Key >::value >>
|
typename BasicJsonType::string_t, Key >::value >>
|
||||||
void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m)
|
void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m)
|
||||||
{
|
{
|
||||||
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
|
|
||||||
if (from_json_enum_keyed_object(j, m, std::is_enum<Key> {}))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
from_json_pair_array_to_map(j, m);
|
from_json_pair_array_to_map(j, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,11 +543,6 @@ template < typename BasicJsonType, typename Key, typename Value, typename Hash,
|
|||||||
typename BasicJsonType::string_t, Key >::value >>
|
typename BasicJsonType::string_t, Key >::value >>
|
||||||
void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)
|
void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)
|
||||||
{
|
{
|
||||||
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
|
|
||||||
if (from_json_enum_keyed_object(j, m, std::is_enum<Key> {}))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
from_json_pair_array_to_map(j, m);
|
from_json_pair_array_to_map(j, m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -329,8 +329,8 @@ boundaries compute_boundaries(FloatType value)
|
|||||||
//
|
//
|
||||||
// -e <= 60 or e >= -60 := alpha
|
// -e <= 60 or e >= -60 := alpha
|
||||||
|
|
||||||
JSON_INLINE_VARIABLE constexpr int kAlpha = -60;
|
constexpr int kAlpha = -60;
|
||||||
JSON_INLINE_VARIABLE constexpr int kGamma = -32;
|
constexpr int kGamma = -32;
|
||||||
|
|
||||||
struct cached_power // c = f * 2^e ~= 10^k
|
struct cached_power // c = f * 2^e ~= 10^k
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <optional> // optional
|
#include <optional> // optional
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <algorithm> // copy
|
||||||
#include <iterator> // begin, end
|
#include <iterator> // begin, end
|
||||||
#include <memory> // allocator_traits
|
#include <memory> // allocator_traits
|
||||||
#include <string> // basic_string, char_traits
|
#include <string> // basic_string, char_traits
|
||||||
@@ -22,7 +23,6 @@
|
|||||||
#include <valarray> // valarray
|
#include <valarray> // valarray
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
#include <nlohmann/detail/exceptions.hpp>
|
|
||||||
#include <nlohmann/detail/iterators/iteration_proxy.hpp>
|
#include <nlohmann/detail/iterators/iteration_proxy.hpp>
|
||||||
#include <nlohmann/detail/meta/cpp_future.hpp>
|
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||||
#include <nlohmann/detail/meta/std_fs.hpp>
|
#include <nlohmann/detail/meta/std_fs.hpp>
|
||||||
@@ -38,14 +38,10 @@ namespace detail
|
|||||||
//////////////////
|
//////////////////
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note all external_constructor<>::construct functions need to store the new
|
* Note all external_constructor<>::construct functions need to call
|
||||||
* value with j.replace_value(), which destroys the old one to avoid a memory
|
* j.m_data.m_value.destroy(j.m_data.m_type) to avoid a memory leak in case j contains an
|
||||||
* leak in case j contains an allocated value (e.g., a string). See bug issue
|
* allocated value (e.g., a string). See bug issue
|
||||||
* https://github.com/nlohmann/json/issues/2865 for more information.
|
* https://github.com/nlohmann/json/issues/2865 for more information.
|
||||||
*
|
|
||||||
* A value that has to be allocated is created before the old one is destroyed:
|
|
||||||
* were it the other way around, an exception while creating the new value would
|
|
||||||
* leave j with the type of the new value, but the pointer to the destroyed old one.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
template<value_t> struct external_constructor;
|
template<value_t> struct external_constructor;
|
||||||
@@ -56,7 +52,10 @@ struct external_constructor<value_t::boolean>
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept
|
static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept
|
||||||
{
|
{
|
||||||
j.replace_value(value_t::boolean, b);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
|
j.m_data.m_type = value_t::boolean;
|
||||||
|
j.m_data.m_value = b;
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,15 +65,19 @@ struct external_constructor<value_t::string>
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
|
static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
|
||||||
{
|
{
|
||||||
const typename BasicJsonType::json_value value(s);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.replace_value(value_t::string, value);
|
j.m_data.m_type = value_t::string;
|
||||||
|
j.m_data.m_value = s;
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)
|
static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)
|
||||||
{
|
{
|
||||||
const typename BasicJsonType::json_value value(std::move(s));
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.replace_value(value_t::string, value);
|
j.m_data.m_type = value_t::string;
|
||||||
|
j.m_data.m_value = std::move(s);
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename BasicJsonType, typename CompatibleStringType,
|
template < typename BasicJsonType, typename CompatibleStringType,
|
||||||
@@ -82,8 +85,10 @@ struct external_constructor<value_t::string>
|
|||||||
int > = 0 >
|
int > = 0 >
|
||||||
static void construct(BasicJsonType& j, const CompatibleStringType& str)
|
static void construct(BasicJsonType& j, const CompatibleStringType& str)
|
||||||
{
|
{
|
||||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::string_t>(str));
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.replace_value(value_t::string, value);
|
j.m_data.m_type = value_t::string;
|
||||||
|
j.m_data.m_value.string = j.template create<typename BasicJsonType::string_t>(str);
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,15 +98,19 @@ struct external_constructor<value_t::binary>
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b)
|
static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b)
|
||||||
{
|
{
|
||||||
const typename BasicJsonType::json_value value(b);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.replace_value(value_t::binary, value);
|
j.m_data.m_type = value_t::binary;
|
||||||
|
j.m_data.m_value = typename BasicJsonType::binary_t(b);
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b)
|
static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b)
|
||||||
{
|
{
|
||||||
const typename BasicJsonType::json_value value(std::move(b));
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.replace_value(value_t::binary, value);
|
j.m_data.m_type = value_t::binary;
|
||||||
|
j.m_data.m_value = typename BasicJsonType::binary_t(std::move(b));
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -111,7 +120,10 @@ struct external_constructor<value_t::number_float>
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept
|
static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept
|
||||||
{
|
{
|
||||||
j.replace_value(value_t::number_float, val);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
|
j.m_data.m_type = value_t::number_float;
|
||||||
|
j.m_data.m_value = val;
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -121,7 +133,10 @@ struct external_constructor<value_t::number_unsigned>
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept
|
static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept
|
||||||
{
|
{
|
||||||
j.replace_value(value_t::number_unsigned, val);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
|
j.m_data.m_type = value_t::number_unsigned;
|
||||||
|
j.m_data.m_value = val;
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,7 +146,10 @@ struct external_constructor<value_t::number_integer>
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept
|
static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept
|
||||||
{
|
{
|
||||||
j.replace_value(value_t::number_integer, val);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
|
j.m_data.m_type = value_t::number_integer;
|
||||||
|
j.m_data.m_value = val;
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -141,15 +159,21 @@ struct external_constructor<value_t::array>
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
|
static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
|
||||||
{
|
{
|
||||||
const typename BasicJsonType::json_value value(arr);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.replace_value(value_t::array, value);
|
j.m_data.m_type = value_t::array;
|
||||||
|
j.m_data.m_value = arr;
|
||||||
|
j.set_parents();
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
|
static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
|
||||||
{
|
{
|
||||||
const typename BasicJsonType::json_value value(std::move(arr));
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.replace_value(value_t::array, value);
|
j.m_data.m_type = value_t::array;
|
||||||
|
j.m_data.m_value = std::move(arr);
|
||||||
|
j.set_parents();
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename BasicJsonType, typename CompatibleArrayType,
|
template < typename BasicJsonType, typename CompatibleArrayType,
|
||||||
@@ -163,23 +187,39 @@ struct external_constructor<value_t::array>
|
|||||||
using std::begin;
|
using std::begin;
|
||||||
using std::end;
|
using std::end;
|
||||||
|
|
||||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr)));
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.replace_value(value_t::array, value);
|
j.m_data.m_type = value_t::array;
|
||||||
|
j.m_data.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
|
||||||
|
j.set_parents();
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, const std::vector<bool>& arr)
|
static void construct(BasicJsonType& j, const std::vector<bool>& arr)
|
||||||
{
|
{
|
||||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(arr.begin(), arr.end()));
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.replace_value(value_t::array, value);
|
j.m_data.m_type = value_t::array;
|
||||||
|
j.m_data.m_value = value_t::array;
|
||||||
|
j.m_data.m_value.array->reserve(arr.size());
|
||||||
|
for (const bool x : arr)
|
||||||
|
{
|
||||||
|
j.m_data.m_value.array->push_back(x);
|
||||||
|
j.set_parent(j.m_data.m_value.array->back());
|
||||||
|
}
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType, typename T,
|
template<typename BasicJsonType, typename T,
|
||||||
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
|
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
|
||||||
static void construct(BasicJsonType& j, const std::valarray<T>& arr)
|
static void construct(BasicJsonType& j, const std::valarray<T>& arr)
|
||||||
{
|
{
|
||||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(std::begin(arr), std::end(arr)));
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.replace_value(value_t::array, value);
|
j.m_data.m_type = value_t::array;
|
||||||
|
j.m_data.m_value = value_t::array;
|
||||||
|
j.m_data.m_value.array->resize(arr.size());
|
||||||
|
std::copy(std::begin(arr), std::end(arr), j.m_data.m_value.array->begin());
|
||||||
|
j.set_parents();
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
||||||
@@ -187,15 +227,18 @@ struct external_constructor<value_t::array>
|
|||||||
enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0>
|
enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0>
|
||||||
static void construct(BasicJsonType& j, CompatibleArrayType && arr)
|
static void construct(BasicJsonType& j, CompatibleArrayType && arr)
|
||||||
{
|
{
|
||||||
// no range constructor: a view's begin() and end() may have different
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
// types, and the view may only be iterable once
|
j.m_data.m_type = value_t::array;
|
||||||
typename BasicJsonType::array_t elements;
|
j.m_data.m_value = value_t::array;
|
||||||
for (auto&& x : std::forward<CompatibleArrayType>(arr))
|
for (auto&& x : std::forward<CompatibleArrayType>(arr))
|
||||||
{
|
{
|
||||||
elements.push_back(x);
|
j.m_data.m_value.array->push_back(x);
|
||||||
}
|
}
|
||||||
const typename BasicJsonType::json_value value(std::move(elements));
|
// set the parents only once all elements are in place: a push_back
|
||||||
j.replace_value(value_t::array, value);
|
// that reallocates moves the earlier elements, which does not keep
|
||||||
|
// their parent pointers
|
||||||
|
j.set_parents();
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
@@ -206,15 +249,21 @@ struct external_constructor<value_t::object>
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
|
static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
|
||||||
{
|
{
|
||||||
const typename BasicJsonType::json_value value(obj);
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.replace_value(value_t::object, value);
|
j.m_data.m_type = value_t::object;
|
||||||
|
j.m_data.m_value = obj;
|
||||||
|
j.set_parents();
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
|
static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
|
||||||
{
|
{
|
||||||
const typename BasicJsonType::json_value value(std::move(obj));
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.replace_value(value_t::object, value);
|
j.m_data.m_type = value_t::object;
|
||||||
|
j.m_data.m_value = std::move(obj);
|
||||||
|
j.set_parents();
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename BasicJsonType, typename CompatibleObjectType,
|
template < typename BasicJsonType, typename CompatibleObjectType,
|
||||||
@@ -224,8 +273,11 @@ struct external_constructor<value_t::object>
|
|||||||
using std::begin;
|
using std::begin;
|
||||||
using std::end;
|
using std::end;
|
||||||
|
|
||||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj)));
|
j.m_data.m_value.destroy(j.m_data.m_type);
|
||||||
j.replace_value(value_t::object, value);
|
j.m_data.m_type = value_t::object;
|
||||||
|
j.m_data.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
|
||||||
|
j.set_parents();
|
||||||
|
j.assert_invariant();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -234,20 +286,9 @@ struct external_constructor<value_t::object>
|
|||||||
/////////////
|
/////////////
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
#ifdef JSON_HAS_CPP_17
|
||||||
// whether storing the value of a std::optional<T> cannot throw; MSVC 2017
|
|
||||||
// evaluates std::is_nothrow_assignable as true even if T's to_json throws, so
|
|
||||||
// the exception would call std::terminate (#5642)
|
|
||||||
#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920
|
|
||||||
template<typename BasicJsonType, typename T>
|
|
||||||
using is_nothrow_optional_to_json = std::false_type;
|
|
||||||
#else
|
|
||||||
template<typename BasicJsonType, typename T>
|
|
||||||
using is_nothrow_optional_to_json = std::is_nothrow_assignable<BasicJsonType&, const T&>;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template<typename BasicJsonType, typename T,
|
template<typename BasicJsonType, typename T,
|
||||||
enable_if_t<std::is_constructible<BasicJsonType, T>::value, int> = 0>
|
enable_if_t<std::is_constructible<BasicJsonType, T>::value, int> = 0>
|
||||||
void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(is_nothrow_optional_to_json<BasicJsonType, T>::value)
|
void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(std::is_nothrow_assignable<BasicJsonType&, const T&>::value)
|
||||||
{
|
{
|
||||||
if (opt.has_value())
|
if (opt.has_value())
|
||||||
{
|
{
|
||||||
@@ -323,7 +364,7 @@ inline void to_json(BasicJsonType& j, EnumType e) noexcept
|
|||||||
{
|
{
|
||||||
using underlying_type = typename std::underlying_type<EnumType>::type;
|
using underlying_type = typename std::underlying_type<EnumType>::type;
|
||||||
static constexpr value_t integral_value_t = std::is_unsigned<underlying_type>::value ? value_t::number_unsigned : value_t::number_integer;
|
static constexpr value_t integral_value_t = std::is_unsigned<underlying_type>::value ? value_t::number_unsigned : value_t::number_integer;
|
||||||
external_constructor<integral_value_t>::construct(j, bool_aware_static_cast<underlying_type>(e));
|
external_constructor<integral_value_t>::construct(j, static_cast<underlying_type>(e));
|
||||||
}
|
}
|
||||||
#endif // JSON_DISABLE_ENUM_SERIALIZATION
|
#endif // JSON_DISABLE_ENUM_SERIALIZATION
|
||||||
|
|
||||||
@@ -343,9 +384,6 @@ template < typename BasicJsonType, typename CompatibleArrayType,
|
|||||||
!is_basic_json<CompatibleArrayType>::value
|
!is_basic_json<CompatibleArrayType>::value
|
||||||
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
||||||
&& !is_compatible_range_view<CompatibleArrayType>::value
|
&& !is_compatible_range_view<CompatibleArrayType>::value
|
||||||
#endif
|
|
||||||
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
&& !is_enum_keyed_map<CompatibleArrayType>::value
|
|
||||||
#endif
|
#endif
|
||||||
,
|
,
|
||||||
int > = 0 >
|
int > = 0 >
|
||||||
@@ -400,33 +438,6 @@ inline void to_json(BasicJsonType& j, const CompatibleObjectType& obj)
|
|||||||
external_constructor<value_t::object>::construct(j, obj);
|
external_constructor<value_t::object>::construct(j, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
// store a map with enum keys as an object, using the enum's own to_json for the
|
|
||||||
// keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); without the macro, such maps
|
|
||||||
// are stored as arrays of [key, value] pairs
|
|
||||||
template < typename BasicJsonType, typename EnumKeyedMap,
|
|
||||||
enable_if_t < is_enum_keyed_map<EnumKeyedMap>::value&& !is_basic_json<EnumKeyedMap>::value, int > = 0 >
|
|
||||||
inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
|
|
||||||
{
|
|
||||||
typename BasicJsonType::object_t obj;
|
|
||||||
for (const auto& p : map)
|
|
||||||
{
|
|
||||||
BasicJsonType key = p.first;
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!key.is_string()))
|
|
||||||
{
|
|
||||||
JSON_THROW(type_error::create(302, concat("type must be string, but is ", key.type_name()), &key));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& key_string = *key.template get_ptr<typename BasicJsonType::string_t*>();
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!obj.emplace(key_string, BasicJsonType(p.second)).second))
|
|
||||||
{
|
|
||||||
JSON_THROW(type_error::create(318, concat("duplicate object key '", key_string, "'"), &key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
external_constructor<value_t::object>::construct(j, std::move(obj));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
|
inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,10 +35,8 @@ std::size_t hash_iteratively(const BasicJsonType& j);
|
|||||||
@brief hash a JSON value
|
@brief hash a JSON value
|
||||||
|
|
||||||
The hash function tries to rely on std::hash where possible. Furthermore, the
|
The hash function tries to rely on std::hash where possible. Furthermore, the
|
||||||
type of the JSON value is taken into account, so null, false, and numbers may
|
type of the JSON value is taken into account to have different hash values for
|
||||||
hash differently from each other, but any two numbers that compare equal
|
null, 0, 0U, and false, etc.
|
||||||
under operator== hash equally regardless of which of number_integer,
|
|
||||||
number_unsigned, or number_float actually holds the value.
|
|
||||||
|
|
||||||
Hashing an array or an object hashes its elements, which used to call this
|
Hashing an array or an object hashes its elements, which used to call this
|
||||||
function again once per nesting level, so a value nested deeply enough
|
function again once per nesting level, so a value nested deeply enough
|
||||||
@@ -57,6 +55,8 @@ template<typename BasicJsonType>
|
|||||||
std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
|
std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
|
||||||
{
|
{
|
||||||
using string_t = typename BasicJsonType::string_t;
|
using string_t = typename BasicJsonType::string_t;
|
||||||
|
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||||
|
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||||
using number_float_t = typename BasicJsonType::number_float_t;
|
using number_float_t = typename BasicJsonType::number_float_t;
|
||||||
|
|
||||||
const auto type = static_cast<std::size_t>(j.type());
|
const auto type = static_cast<std::size_t>(j.type());
|
||||||
@@ -113,24 +113,21 @@ std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case BasicJsonType::value_t::number_integer:
|
case BasicJsonType::value_t::number_integer:
|
||||||
|
{
|
||||||
|
const auto h = std::hash<number_integer_t> {}(j.template get<number_integer_t>());
|
||||||
|
return combine(type, h);
|
||||||
|
}
|
||||||
|
|
||||||
case BasicJsonType::value_t::number_unsigned:
|
case BasicJsonType::value_t::number_unsigned:
|
||||||
|
{
|
||||||
|
const auto h = std::hash<number_unsigned_t> {}(j.template get<number_unsigned_t>());
|
||||||
|
return combine(type, h);
|
||||||
|
}
|
||||||
|
|
||||||
case BasicJsonType::value_t::number_float:
|
case BasicJsonType::value_t::number_float:
|
||||||
{
|
{
|
||||||
// operator== compares numbers by their mathematical value across
|
const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());
|
||||||
// number_integer, number_unsigned, and number_float, so equal
|
return combine(type, h);
|
||||||
// numbers of different internal types (0, 0U, 0.0) must hash the
|
|
||||||
// same. Two equal numbers have the same value, which converts to
|
|
||||||
// the same number_float_t, so all numbers share one type tag and
|
|
||||||
// hash that converted value. Adding zero turns -0.0 (equal to 0)
|
|
||||||
// into 0.0, as std::hash need not map both to the same hash.
|
|
||||||
// The converse does not hold: converting a number_float_t value
|
|
||||||
// to an integer type is lossy, so the result can hash
|
|
||||||
// differently, and unequal numbers that convert to the same
|
|
||||||
// number_float_t (e.g., 2^53 and 2^53 + 1) share a hash.
|
|
||||||
const auto number_type = static_cast<std::size_t>(BasicJsonType::value_t::number_float);
|
|
||||||
const auto value = j.template get<number_float_t>() + static_cast<number_float_t>(0);
|
|
||||||
const auto h = std::hash<number_float_t> {}(value);
|
|
||||||
return combine(number_type, h);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case BasicJsonType::value_t::binary:
|
case BasicJsonType::value_t::binary:
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -221,7 +221,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// scan functions
|
// scan functions
|
||||||
/////////////////////
|
/////////////////////
|
||||||
|
|
||||||
/// contiguous input: try to decode the 4 hex digits following `\\u`
|
/// contiguous input: try to decode the 4 hex digits following `\u`
|
||||||
/// directly from the input buffer via hex_codepoint(), instead of 4 calls
|
/// directly from the input buffer via hex_codepoint(), instead of 4 calls
|
||||||
/// to get(). On success, advances the adapter and the position counters
|
/// to get(). On success, advances the adapter and the position counters
|
||||||
/// exactly as those 4 get() calls would (a hex digit is never '\n', so
|
/// exactly as those 4 get() calls would (a hex digit is never '\n', so
|
||||||
@@ -2070,39 +2070,6 @@ scan_number_done:
|
|||||||
// read the next character and ignore whitespace
|
// read the next character and ignore whitespace
|
||||||
skip_whitespace();
|
skip_whitespace();
|
||||||
|
|
||||||
return scan_after_whitespace();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief scan the next token when the caller expects a separator (':' or
|
|
||||||
',') most of the time
|
|
||||||
|
|
||||||
After an object key the next token is almost always ':', after a value
|
|
||||||
inside an object or array almost always ','. Testing for that character
|
|
||||||
first is a compare and a well-predicted branch, where the switch in
|
|
||||||
scan_after_whitespace() is an indirect jump through a table. Anything else
|
|
||||||
goes through the switch, so the result is the same as scan()'s.
|
|
||||||
|
|
||||||
May only be called after scan() has run once (the BOM check is skipped).
|
|
||||||
*/
|
|
||||||
token_type scan_expecting(token_type expected_type)
|
|
||||||
{
|
|
||||||
JSON_ASSERT(expected_type == token_type::name_separator || expected_type == token_type::value_separator);
|
|
||||||
JSON_ASSERT(position.chars_read_total > 0);
|
|
||||||
const char_int_type expected_char = static_cast<unsigned char>((expected_type == token_type::name_separator) ? ':' : ',');
|
|
||||||
skip_whitespace();
|
|
||||||
if (JSON_HEDLEY_LIKELY(current == expected_char))
|
|
||||||
{
|
|
||||||
return expected_type;
|
|
||||||
}
|
|
||||||
return scan_after_whitespace();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// the part of scan() after the leading whitespace: skip comments and
|
|
||||||
/// scan the token that starts with current
|
|
||||||
token_type scan_after_whitespace()
|
|
||||||
{
|
|
||||||
// ignore comments
|
// ignore comments
|
||||||
while (ignore_comments && current == '/')
|
while (ignore_comments && current == '/')
|
||||||
{
|
{
|
||||||
@@ -2182,6 +2149,7 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
/// input adapter
|
/// input adapter
|
||||||
InputAdapterType ia;
|
InputAdapterType ia;
|
||||||
|
|
||||||
|
|||||||
@@ -988,10 +988,10 @@ void convert_float_locale_aware(StringType& token, std::size_t decimal_point_pos
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::string buffer(token.data(), token.size());
|
std::string copy(token.data(), token.size());
|
||||||
buffer.replace(decimal_point_position, 1, decimal_point);
|
copy.replace(decimal_point_position, 1, decimal_point);
|
||||||
strtof_by_type(value, buffer.c_str(), &endptr);
|
strtof_by_type(value, copy.c_str(), &endptr);
|
||||||
complete = endptr == buffer.c_str() + buffer.size();
|
complete = endptr == copy.c_str() + copy.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_LIKELY(complete))
|
if (JSON_HEDLEY_LIKELY(complete))
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -423,7 +423,7 @@ class parser
|
|||||||
{
|
{
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of array (ignore_trailing_commas = true)
|
// or end of array (ignore_trailing_commas = true)
|
||||||
if (get_token_expecting(token_type::value_separator))
|
if (get_token() == token_type::value_separator)
|
||||||
{
|
{
|
||||||
// parse a new value
|
// parse a new value
|
||||||
get_token();
|
get_token();
|
||||||
@@ -463,7 +463,7 @@ class parser
|
|||||||
|
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of object (ignore_trailing_commas = true)
|
// or end of object (ignore_trailing_commas = true)
|
||||||
if (get_token_expecting(token_type::value_separator))
|
if (get_token() == token_type::value_separator)
|
||||||
{
|
{
|
||||||
get_token();
|
get_token();
|
||||||
|
|
||||||
@@ -484,7 +484,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
return sax->parse_error(m_lexer.get_position(),
|
||||||
m_lexer.get_token_string(),
|
m_lexer.get_token_string(),
|
||||||
@@ -528,13 +528,6 @@ class parser
|
|||||||
return last_token = m_lexer.scan();
|
return last_token = m_lexer.scan();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get next token from lexer; true if it is the separator @a expected_type
|
|
||||||
/// (name_separator or value_separator), which it usually is
|
|
||||||
bool get_token_expecting(token_type expected_type)
|
|
||||||
{
|
|
||||||
return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string exception_message(const token_type expected, const std::string& context)
|
std::string exception_message(const token_type expected, const std::string& context)
|
||||||
{
|
{
|
||||||
std::string error_msg = "syntax error ";
|
std::string error_msg = "syntax error ";
|
||||||
|
|||||||
@@ -22,10 +22,6 @@ namespace detail
|
|||||||
constexpr std::int64_t pow5_128_smallest_power = -342;
|
constexpr std::int64_t pow5_128_smallest_power = -342;
|
||||||
constexpr std::int64_t pow5_128_largest_power = 308;
|
constexpr std::int64_t pow5_128_largest_power = 308;
|
||||||
|
|
||||||
// every entry of pow5_128() holds two 64-bit halves of 5^q, one per covered power of 5
|
|
||||||
static_assert((pow5_128_largest_power - pow5_128_smallest_power + 1) * 2 == 1302,
|
|
||||||
"pow5_128_smallest_power/pow5_128_largest_power must match the size of the pow5_128() table");
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief 128-bit approximations of 5^q for q in [-342, 308]
|
@brief 128-bit approximations of 5^q for q in [-342, 308]
|
||||||
|
|
||||||
|
|||||||
@@ -88,13 +88,9 @@ class json_pointer
|
|||||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_string_t/
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_string_t/
|
||||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string())
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string())
|
||||||
operator string_t() const
|
operator string_t() const
|
||||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
|
||||||
= delete;
|
|
||||||
#else
|
|
||||||
{
|
{
|
||||||
return to_string();
|
return to_string();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef JSON_NO_IO
|
#ifndef JSON_NO_IO
|
||||||
/// @brief write string representation of the JSON pointer to stream
|
/// @brief write string representation of the JSON pointer to stream
|
||||||
@@ -1033,13 +1029,9 @@ class json_pointer
|
|||||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
|
||||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer))
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer))
|
||||||
bool operator==(const string_t& rhs) const
|
bool operator==(const string_t& rhs) const
|
||||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
|
||||||
= delete;
|
|
||||||
#else
|
|
||||||
{
|
{
|
||||||
return *this == json_pointer(rhs);
|
return *this == json_pointer(rhs);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/// @brief 3-way compares two JSON pointers
|
/// @brief 3-way compares two JSON pointers
|
||||||
template<typename RefStringTypeRhs>
|
template<typename RefStringTypeRhs>
|
||||||
@@ -1117,26 +1109,18 @@ template<typename RefStringTypeLhs,
|
|||||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
|
||||||
inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
||||||
const StringType& rhs)
|
const StringType& rhs)
|
||||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
|
||||||
= delete;
|
|
||||||
#else
|
|
||||||
{
|
{
|
||||||
return lhs == json_pointer<RefStringTypeLhs>(rhs);
|
return lhs == json_pointer<RefStringTypeLhs>(rhs);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
template<typename RefStringTypeRhs,
|
template<typename RefStringTypeRhs,
|
||||||
typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
|
typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
|
||||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
|
||||||
inline bool operator==(const StringType& lhs,
|
inline bool operator==(const StringType& lhs,
|
||||||
const json_pointer<RefStringTypeRhs>& rhs)
|
const json_pointer<RefStringTypeRhs>& rhs)
|
||||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
|
||||||
= delete;
|
|
||||||
#else
|
|
||||||
{
|
{
|
||||||
return json_pointer<RefStringTypeRhs>(lhs) == rhs;
|
return json_pointer<RefStringTypeRhs>(lhs) == rhs;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
||||||
inline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
|
inline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
|
||||||
@@ -1150,26 +1134,18 @@ template<typename RefStringTypeLhs,
|
|||||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))
|
||||||
inline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
|
inline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
|
||||||
const StringType& rhs)
|
const StringType& rhs)
|
||||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
|
||||||
= delete;
|
|
||||||
#else
|
|
||||||
{
|
{
|
||||||
return !(lhs == rhs);
|
return !(lhs == rhs);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
template<typename RefStringTypeRhs,
|
template<typename RefStringTypeRhs,
|
||||||
typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
|
typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
|
||||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))
|
||||||
inline bool operator!=(const StringType& lhs,
|
inline bool operator!=(const StringType& lhs,
|
||||||
const json_pointer<RefStringTypeRhs>& rhs)
|
const json_pointer<RefStringTypeRhs>& rhs)
|
||||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
|
||||||
= delete;
|
|
||||||
#else
|
|
||||||
{
|
{
|
||||||
return !(lhs == rhs);
|
return !(lhs == rhs);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
||||||
inline bool operator<(const json_pointer<RefStringTypeLhs>& lhs,
|
inline bool operator<(const json_pointer<RefStringTypeLhs>& lhs,
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ class json_ref
|
|||||||
|
|
||||||
json_ref(std::initializer_list<json_ref> init)
|
json_ref(std::initializer_list<json_ref> init)
|
||||||
: owned_value(init)
|
: owned_value(init)
|
||||||
, braced_list(true)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
template <
|
template <
|
||||||
@@ -70,17 +69,9 @@ class json_ref
|
|||||||
return &** this;
|
return &** this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// whether the value was written as a braced list, such as {"key", 1},
|
|
||||||
/// rather than given as a value
|
|
||||||
bool is_braced_list() const noexcept
|
|
||||||
{
|
|
||||||
return braced_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mutable value_type owned_value = nullptr;
|
mutable value_type owned_value = nullptr;
|
||||||
value_type const* value_ref = nullptr;
|
value_type const* value_ref = nullptr;
|
||||||
bool braced_list = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|||||||
@@ -888,10 +888,6 @@
|
|||||||
#define JSON_DISABLE_ENUM_SERIALIZATION 0
|
#define JSON_DISABLE_ENUM_SERIALIZATION 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef JSON_DELETE_DEPRECATED_FUNCTIONS
|
|
||||||
#define JSON_DELETE_DEPRECATED_FUNCTIONS 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
|
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
|
||||||
#endif
|
#endif
|
||||||
@@ -45,8 +45,6 @@
|
|||||||
#undef JSON_PRECISE_STREAM_POSITION
|
#undef JSON_PRECISE_STREAM_POSITION
|
||||||
#undef JSON_STRICT_NUL_HANDLING
|
#undef JSON_STRICT_NUL_HANDLING
|
||||||
#undef JSON_STRICT_BINARY_UTF8
|
#undef JSON_STRICT_BINARY_UTF8
|
||||||
#undef JSON_DELETE_DEPRECATED_FUNCTIONS
|
|
||||||
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
||||||
@@ -438,30 +438,6 @@ template<typename BasicJsonType, typename CompatibleObjectType>
|
|||||||
struct is_compatible_object_type
|
struct is_compatible_object_type
|
||||||
: is_compatible_object_type_impl<BasicJsonType, CompatibleObjectType> {};
|
: is_compatible_object_type_impl<BasicJsonType, CompatibleObjectType> {};
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
using insert_result_t = decltype(std::declval<T&>().insert(std::declval<const value_type_t<T>&>()));
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
using insert_result_second_t = decltype(std::declval<T&>().insert(std::declval<const value_type_t<T>&>()).second);
|
|
||||||
|
|
||||||
// a map-like type (std::map, std::unordered_map, ...) whose keys are enums; see
|
|
||||||
// JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
template<typename T, typename = void>
|
|
||||||
struct is_enum_keyed_map : std::false_type {};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
struct is_enum_keyed_map <
|
|
||||||
T, enable_if_t < is_detected<mapped_type_t, T>::value&&
|
|
||||||
is_detected<key_type_t, T>::value >>
|
|
||||||
{
|
|
||||||
// maps with non-unique keys (std::multimap, std::unordered_multimap, ...)
|
|
||||||
// are excluded, because an object cannot hold duplicate keys; they are
|
|
||||||
// detected by insert() returning an iterator instead of a pair<iterator, bool>
|
|
||||||
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
|
|
||||||
static constexpr bool value = std::is_enum<typename T::key_type>::value &&
|
|
||||||
!(is_detected<insert_result_t, T>::value && !is_detected<insert_result_second_t, T>::value);
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename BasicJsonType, typename ConstructibleObjectType,
|
template<typename BasicJsonType, typename ConstructibleObjectType,
|
||||||
typename = void>
|
typename = void>
|
||||||
struct is_constructible_object_type_impl : std::false_type {};
|
struct is_constructible_object_type_impl : std::false_type {};
|
||||||
@@ -906,21 +882,6 @@ T conditional_static_cast(U value)
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// like conditional_static_cast, but converts to bool by comparing with zero,
|
|
||||||
// because MSVC 2015 warns about any conversion to bool (C4800), even with an
|
|
||||||
// explicit cast; used for enums whose underlying type is bool
|
|
||||||
template < typename T, typename U, enable_if_t < !std::is_same<T, bool>::value, int > = 0 >
|
|
||||||
T bool_aware_static_cast(U value)
|
|
||||||
{
|
|
||||||
return conditional_static_cast<T>(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename U, enable_if_t<std::is_same<T, bool>::value, int> = 0>
|
|
||||||
bool bool_aware_static_cast(U value)
|
|
||||||
{
|
|
||||||
return value != U();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Types>
|
template<typename... Types>
|
||||||
using all_integral = conjunction<std::is_integral<Types>...>;
|
using all_integral = conjunction<std::is_integral<Types>...>;
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/output/error_handler.hpp>
|
#include <nlohmann/detail/output/error_handler.hpp>
|
||||||
#include <nlohmann/detail/output/output_adapters.hpp>
|
#include <nlohmann/detail/output/output_adapters.hpp>
|
||||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
|
||||||
#include <nlohmann/detail/string_concat.hpp>
|
#include <nlohmann/detail/string_concat.hpp>
|
||||||
#include <nlohmann/detail/string_utils.hpp>
|
#include <nlohmann/detail/string_utils.hpp>
|
||||||
|
|
||||||
@@ -128,7 +127,6 @@ class binary_writer
|
|||||||
@throw type_error.316 if a string value or an object key is not valid
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
@throw type_error.317 if @a j is not an object
|
@throw type_error.317 if @a j is not an object
|
||||||
@throw type_error.321 if a value nested in @a j is discarded
|
|
||||||
*/
|
*/
|
||||||
void write_bson(const BasicJsonType& j)
|
void write_bson(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -158,29 +156,11 @@ class binary_writer
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] j JSON value to serialize
|
@param[in] j JSON value to serialize
|
||||||
@param[in] depth nesting level of @a j, counted from the top-level value
|
|
||||||
passed to @ref basic_json::to_cbor
|
|
||||||
@throw type_error.316 if a string value or an object key is not valid
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
|
||||||
|
|
||||||
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
|
|
||||||
levels have been entered, @ref write_cbor_iterative 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.
|
|
||||||
|
|
||||||
@sa https://github.com/nlohmann/json/issues/5392
|
|
||||||
*/
|
*/
|
||||||
void write_cbor(const BasicJsonType& j, const std::size_t depth = 0)
|
void write_cbor(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()) && (j.is_array() || j.is_object()))
|
|
||||||
{
|
|
||||||
write_cbor_iterative(j);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (j.type())
|
switch (j.type())
|
||||||
{
|
{
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
@@ -262,9 +242,10 @@ class binary_writer
|
|||||||
// step 1: write control byte and the array size
|
// step 1: write control byte and the array size
|
||||||
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
||||||
|
|
||||||
|
// step 2: write each element
|
||||||
for (const auto& el : *j.m_data.m_value.array)
|
for (const auto& el : *j.m_data.m_value.array)
|
||||||
{
|
{
|
||||||
write_cbor(el, depth + 1);
|
write_cbor(el);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -319,6 +300,7 @@ class binary_writer
|
|||||||
// step 1: write control byte and the object size
|
// step 1: write control byte and the object size
|
||||||
write_cbor_head(0xA0, j.m_data.m_value.object->size());
|
write_cbor_head(0xA0, j.m_data.m_value.object->size());
|
||||||
|
|
||||||
|
// step 2: write each element
|
||||||
for (const auto& el : *j.m_data.m_value.object)
|
for (const auto& el : *j.m_data.m_value.object)
|
||||||
{
|
{
|
||||||
// el.first is checked here, against the object as
|
// el.first is checked here, against the object as
|
||||||
@@ -333,14 +315,14 @@ class binary_writer
|
|||||||
check_utf8(el.first, j);
|
check_utf8(el.first, j);
|
||||||
}
|
}
|
||||||
write_cbor(el.first);
|
write_cbor(el.first);
|
||||||
write_cbor(el.second, depth + 1);
|
write_cbor(el.second);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
throw_on_discarded(j, "CBOR");
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,21 +382,9 @@ class binary_writer
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] j JSON value to serialize
|
@param[in] j JSON value to serialize
|
||||||
@param[in] depth nesting level of @a j, counted from the top-level value
|
|
||||||
passed to @ref basic_json::to_msgpack
|
|
||||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
|
||||||
|
|
||||||
@sa @ref write_cbor
|
|
||||||
@sa https://github.com/nlohmann/json/issues/5392
|
|
||||||
*/
|
*/
|
||||||
void write_msgpack(const BasicJsonType& j, const std::size_t depth = 0)
|
void write_msgpack(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()) && (j.is_array() || j.is_object()))
|
|
||||||
{
|
|
||||||
write_msgpack_iterative(j);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (j.type())
|
switch (j.type())
|
||||||
{
|
{
|
||||||
case value_t::null: // nil
|
case value_t::null: // nil
|
||||||
@@ -530,11 +500,29 @@ class binary_writer
|
|||||||
case value_t::array:
|
case value_t::array:
|
||||||
{
|
{
|
||||||
// step 1: write control byte and the array size
|
// step 1: write control byte and the array size
|
||||||
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
const auto N = to_msgpack_length(j.m_data.m_value.array->size(), j);
|
||||||
|
if (N <= 15)
|
||||||
|
{
|
||||||
|
// fixarray
|
||||||
|
write_number(static_cast<std::uint8_t>(0x90 | N));
|
||||||
|
}
|
||||||
|
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
||||||
|
{
|
||||||
|
// array 16
|
||||||
|
oa.write_character(to_char_type(0xDC));
|
||||||
|
write_number(static_cast<std::uint16_t>(N));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// array 32
|
||||||
|
oa.write_character(to_char_type(0xDD));
|
||||||
|
write_number(static_cast<std::uint32_t>(N));
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 2: write each element
|
||||||
for (const auto& el : *j.m_data.m_value.array)
|
for (const auto& el : *j.m_data.m_value.array)
|
||||||
{
|
{
|
||||||
write_msgpack(el, depth + 1);
|
write_msgpack(el);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -630,8 +618,26 @@ class binary_writer
|
|||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
// step 1: write control byte and the object size
|
// step 1: write control byte and the object size
|
||||||
write_msgpack_object_prefix(j.m_data.m_value.object->size(), j);
|
const auto N = to_msgpack_length(j.m_data.m_value.object->size(), j);
|
||||||
|
if (N <= 15)
|
||||||
|
{
|
||||||
|
// fixmap
|
||||||
|
write_number(static_cast<std::uint8_t>(0x80 | (N & 0xF)));
|
||||||
|
}
|
||||||
|
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
||||||
|
{
|
||||||
|
// map 16
|
||||||
|
oa.write_character(to_char_type(0xDE));
|
||||||
|
write_number(static_cast<std::uint16_t>(N));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// map 32
|
||||||
|
oa.write_character(to_char_type(0xDF));
|
||||||
|
write_number(static_cast<std::uint32_t>(N));
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 2: write each element
|
||||||
for (const auto& el : *j.m_data.m_value.object)
|
for (const auto& el : *j.m_data.m_value.object)
|
||||||
{
|
{
|
||||||
// as in write_cbor, el.first is checked here against the
|
// as in write_cbor, el.first is checked here against the
|
||||||
@@ -642,14 +648,14 @@ class binary_writer
|
|||||||
check_utf8(el.first, j);
|
check_utf8(el.first, j);
|
||||||
}
|
}
|
||||||
write_msgpack(el.first);
|
write_msgpack(el.first);
|
||||||
write_msgpack(el.second, depth + 1);
|
write_msgpack(el.second);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
throw_on_discarded(j, "MessagePack");
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,26 +666,13 @@ class binary_writer
|
|||||||
@param[in] add_prefix whether prefixes need to be used for this value
|
@param[in] add_prefix whether prefixes need to be used for this value
|
||||||
@param[in] use_bjdata whether write in BJData format, default is false
|
@param[in] use_bjdata whether write in BJData format, default is false
|
||||||
@param[in] bjdata_version which BJData version to use, default is draft2
|
@param[in] bjdata_version which BJData version to use, default is draft2
|
||||||
@param[in] depth nesting level of @a j, counted from the top-level value
|
|
||||||
passed to @ref basic_json::to_ubjson or @ref basic_json::to_bjdata
|
|
||||||
@throw type_error.316 if a string value or an object key is not valid
|
@throw type_error.316 if a string value or an object key is not valid
|
||||||
UTF-8
|
UTF-8
|
||||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
|
||||||
|
|
||||||
@sa @ref write_cbor
|
|
||||||
@sa https://github.com/nlohmann/json/issues/5392
|
|
||||||
*/
|
*/
|
||||||
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||||
const bool use_type, const bool add_prefix = true,
|
const bool use_type, const bool add_prefix = true,
|
||||||
const bool use_bjdata = false, const bjdata_version_t bjdata_version = bjdata_version_t::draft2,
|
const bool use_bjdata = false, const bjdata_version_t bjdata_version = bjdata_version_t::draft2)
|
||||||
const std::size_t depth = 0)
|
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()) && (j.is_array() || j.is_object()))
|
|
||||||
{
|
|
||||||
write_ubjson_iterative(j, use_count, use_type, add_prefix, use_bjdata, bjdata_version);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool bjdata_draft3 = use_bjdata && bjdata_version == bjdata_version_t::draft3;
|
const bool bjdata_draft3 = use_bjdata && bjdata_version == bjdata_version_t::draft3;
|
||||||
|
|
||||||
switch (j.type())
|
switch (j.type())
|
||||||
@@ -740,15 +733,55 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
{
|
{
|
||||||
|
if (add_prefix)
|
||||||
|
{
|
||||||
|
oa.write_character(to_char_type('['));
|
||||||
|
}
|
||||||
|
|
||||||
bool prefix_required = true;
|
bool prefix_required = true;
|
||||||
const bool write_closer = write_ubjson_start_array(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
if (use_type && !j.m_data.m_value.array->empty())
|
||||||
|
{
|
||||||
|
if (!use_count)
|
||||||
|
{
|
||||||
|
JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j));
|
||||||
|
}
|
||||||
|
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
|
||||||
|
const bool same_prefix = std::all_of(j.begin() + 1, j.end(),
|
||||||
|
[this, first_prefix, use_bjdata](const BasicJsonType & v)
|
||||||
|
{
|
||||||
|
return ubjson_prefix(v, use_bjdata) == first_prefix;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// those, at one byte per element, so the result can be read back.
|
||||||
|
// Objects are not affected: every element is preceded by its key.
|
||||||
|
const bool valueless_type = (first_prefix == 'Z' || first_prefix == 'T' || first_prefix == 'F');
|
||||||
|
const bool excessive_valueless = valueless_type
|
||||||
|
&& 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)))
|
||||||
|
{
|
||||||
|
prefix_required = false;
|
||||||
|
oa.write_character(to_char_type('$'));
|
||||||
|
oa.write_character(first_prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (use_count)
|
||||||
|
{
|
||||||
|
oa.write_character(to_char_type('#'));
|
||||||
|
write_number_with_ubjson_prefix(j.m_data.m_value.array->size(), true, use_bjdata);
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto& el : *j.m_data.m_value.array)
|
for (const auto& el : *j.m_data.m_value.array)
|
||||||
{
|
{
|
||||||
write_ubjson(el, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth + 1);
|
write_ubjson(el, use_count, use_type, prefix_required, use_bjdata, bjdata_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (write_closer)
|
if (!use_count)
|
||||||
{
|
{
|
||||||
oa.write_character(to_char_type(']'));
|
oa.write_character(to_char_type(']'));
|
||||||
}
|
}
|
||||||
@@ -806,7 +839,7 @@ class binary_writer
|
|||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
if (use_bjdata && is_bjdata_ndarray(j))
|
if (use_bjdata && j.m_data.m_value.object->size() == 3 && j.m_data.m_value.object->find("_ArrayType_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArraySize_") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find("_ArrayData_") != j.m_data.m_value.object->end())
|
||||||
{
|
{
|
||||||
if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type, bjdata_version)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata)
|
if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type, bjdata_version)) // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata)
|
||||||
{
|
{
|
||||||
@@ -814,370 +847,12 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool prefix_required = true;
|
|
||||||
const bool write_closer = write_ubjson_start_object(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
|
||||||
|
|
||||||
for (const auto& el : *j.m_data.m_value.object)
|
|
||||||
{
|
|
||||||
string_t storage;
|
|
||||||
const string_t& key = sanitize_utf8_for_write(el.first, j, storage);
|
|
||||||
write_number_with_ubjson_prefix(key.size(), true, use_bjdata);
|
|
||||||
oa.write_characters(
|
|
||||||
reinterpret_cast<const CharType*>(key.data()),
|
|
||||||
key.size());
|
|
||||||
write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version, depth + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (write_closer)
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type('}'));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@param[in] j JSON value to serialize
|
|
||||||
*/
|
|
||||||
void write_bon8(const BasicJsonType& j)
|
|
||||||
{
|
|
||||||
bool string_open = false;
|
|
||||||
write_bon8_value(j, string_open);
|
|
||||||
|
|
||||||
// the last string of a message must be terminated
|
|
||||||
if (string_open)
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0xFF));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
/*!
|
|
||||||
@brief throws because @a j is discarded and cannot be serialized
|
|
||||||
@throw type_error.321 always
|
|
||||||
*/
|
|
||||||
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
|
|
||||||
{
|
|
||||||
static_cast<void>(j); // unused when JSON_NOEXCEPTION is defined
|
|
||||||
static_cast<void>(format_name);
|
|
||||||
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j)
|
|
||||||
{
|
|
||||||
const auto n = to_msgpack_length(N, j);
|
|
||||||
if (n <= 15)
|
|
||||||
{
|
|
||||||
// fixarray
|
|
||||||
write_number(static_cast<std::uint8_t>(0x90 | n));
|
|
||||||
}
|
|
||||||
else if (n <= (std::numeric_limits<std::uint16_t>::max)())
|
|
||||||
{
|
|
||||||
// array 16
|
|
||||||
oa.write_character(to_char_type(0xDC));
|
|
||||||
write_number(static_cast<std::uint16_t>(n));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// array 32
|
|
||||||
oa.write_character(to_char_type(0xDD));
|
|
||||||
write_number(static_cast<std::uint32_t>(n));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_msgpack_object_prefix(const std::size_t N, const BasicJsonType& j)
|
|
||||||
{
|
|
||||||
const auto n = to_msgpack_length(N, j);
|
|
||||||
if (n <= 15)
|
|
||||||
{
|
|
||||||
// fixmap
|
|
||||||
write_number(static_cast<std::uint8_t>(0x80 | (n & 0xF)));
|
|
||||||
}
|
|
||||||
else if (n <= (std::numeric_limits<std::uint16_t>::max)())
|
|
||||||
{
|
|
||||||
// map 16
|
|
||||||
oa.write_character(to_char_type(0xDE));
|
|
||||||
write_number(static_cast<std::uint16_t>(n));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// map 32
|
|
||||||
oa.write_character(to_char_type(0xDF));
|
|
||||||
write_number(static_cast<std::uint32_t>(n));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief a CBOR or MessagePack array or object whose elements
|
|
||||||
/// @ref write_cbor_iterative or @ref write_msgpack_iterative is
|
|
||||||
/// still writing
|
|
||||||
struct binary_container_frame
|
|
||||||
{
|
|
||||||
explicit binary_container_frame(const BasicJsonType* value_) noexcept
|
|
||||||
: value(value_)
|
|
||||||
{
|
|
||||||
if (value->is_object())
|
|
||||||
{
|
|
||||||
object_it = value->m_data.m_value.object->cbegin();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
array_it = value->m_data.m_value.array->cbegin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// declared for GCC's -Weffc++, which asks for them in a class with
|
|
||||||
// pointer members and a non-trivial destructor; the exception
|
|
||||||
// specifications are left implicit, as GCC 4.8 rejects explicit ones
|
|
||||||
// that differ from them
|
|
||||||
binary_container_frame(const binary_container_frame&) = default;
|
|
||||||
binary_container_frame(binary_container_frame&&) = default;
|
|
||||||
binary_container_frame& operator=(const binary_container_frame&) = default;
|
|
||||||
binary_container_frame& operator=(binary_container_frame&&) = default;
|
|
||||||
~binary_container_frame() = default;
|
|
||||||
|
|
||||||
/// the array or object being written
|
|
||||||
const BasicJsonType* value;
|
|
||||||
/// value's elements still to write; which of the two is live follows
|
|
||||||
/// from the type of value. They are kept side by side rather than in
|
|
||||||
/// a union, which would need its special members written out by
|
|
||||||
/// hand, see detail/iterators/internal_iterator.hpp
|
|
||||||
typename BasicJsonType::object_t::const_iterator object_it{};
|
|
||||||
typename BasicJsonType::array_t::const_iterator array_it{};
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief write @a j with @ref write_cbor, or write its header and push a
|
|
||||||
frame for @ref write_cbor_iterative to continue with its elements
|
|
||||||
|
|
||||||
A scalar, and an empty array or object, are written out in full: there is
|
|
||||||
nothing below them for @ref write_cbor_iterative to come back to, so
|
|
||||||
nothing is pushed for them.
|
|
||||||
*/
|
|
||||||
void write_cbor_value_or_push(const BasicJsonType& j, std::vector<binary_container_frame>& stack)
|
|
||||||
{
|
|
||||||
if (j.is_array())
|
|
||||||
{
|
|
||||||
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
|
||||||
if (!j.m_data.m_value.array->empty())
|
|
||||||
{
|
|
||||||
stack.emplace_back(&j);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j.is_object())
|
|
||||||
{
|
|
||||||
write_cbor_head(0xA0, j.m_data.m_value.object->size());
|
|
||||||
if (!j.m_data.m_value.object->empty())
|
|
||||||
{
|
|
||||||
stack.emplace_back(&j);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
write_cbor(j);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief write out @a root and everything below it without the call stack
|
|
||||||
|
|
||||||
Emits the same bytes as @ref write_cbor, 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 written for speed.
|
|
||||||
*/
|
|
||||||
void write_cbor_iterative(const BasicJsonType& root)
|
|
||||||
{
|
|
||||||
// only a container with elements is ever pushed; see write_cbor_value_or_push
|
|
||||||
std::vector<binary_container_frame> stack;
|
|
||||||
write_cbor_value_or_push(root, stack);
|
|
||||||
|
|
||||||
while (!stack.empty())
|
|
||||||
{
|
|
||||||
const binary_container_frame current = stack.back();
|
|
||||||
|
|
||||||
if (current.value->is_array())
|
|
||||||
{
|
|
||||||
const auto& array = *current.value->m_data.m_value.array;
|
|
||||||
if (current.array_it == array.cend())
|
|
||||||
{
|
|
||||||
stack.pop_back();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// read the child before pushing: entering it can move every frame
|
|
||||||
const BasicJsonType* child = &(*current.array_it);
|
|
||||||
++stack.back().array_it;
|
|
||||||
write_cbor_value_or_push(*child, stack);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const auto& object = *current.value->m_data.m_value.object;
|
|
||||||
if (current.object_it == object.cend())
|
|
||||||
{
|
|
||||||
stack.pop_back();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// el.first is checked here, against the object as diagnostics
|
|
||||||
// context, like the matching check in write_cbor's object case
|
|
||||||
if (error_handler == error_handler_t::strict)
|
|
||||||
{
|
|
||||||
check_utf8(current.object_it->first, *current.value);
|
|
||||||
}
|
|
||||||
write_cbor(current.object_it->first);
|
|
||||||
const BasicJsonType* child = &(current.object_it->second);
|
|
||||||
++stack.back().object_it;
|
|
||||||
write_cbor_value_or_push(*child, stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief write @a j with @ref write_msgpack, or write its header and push a
|
|
||||||
frame for @ref write_msgpack_iterative to continue with its elements
|
|
||||||
|
|
||||||
@sa @ref write_cbor_value_or_push
|
|
||||||
*/
|
|
||||||
void write_msgpack_value_or_push(const BasicJsonType& j, std::vector<binary_container_frame>& stack)
|
|
||||||
{
|
|
||||||
if (j.is_array())
|
|
||||||
{
|
|
||||||
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
|
||||||
if (!j.m_data.m_value.array->empty())
|
|
||||||
{
|
|
||||||
stack.emplace_back(&j);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (j.is_object())
|
|
||||||
{
|
|
||||||
write_msgpack_object_prefix(j.m_data.m_value.object->size(), j);
|
|
||||||
if (!j.m_data.m_value.object->empty())
|
|
||||||
{
|
|
||||||
stack.emplace_back(&j);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
write_msgpack(j);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief write out @a root and everything below it without the call stack
|
|
||||||
|
|
||||||
@sa @ref write_cbor_iterative
|
|
||||||
*/
|
|
||||||
void write_msgpack_iterative(const BasicJsonType& root)
|
|
||||||
{
|
|
||||||
std::vector<binary_container_frame> stack;
|
|
||||||
write_msgpack_value_or_push(root, stack);
|
|
||||||
|
|
||||||
while (!stack.empty())
|
|
||||||
{
|
|
||||||
const binary_container_frame current = stack.back();
|
|
||||||
|
|
||||||
if (current.value->is_array())
|
|
||||||
{
|
|
||||||
const auto& array = *current.value->m_data.m_value.array;
|
|
||||||
if (current.array_it == array.cend())
|
|
||||||
{
|
|
||||||
stack.pop_back();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BasicJsonType* child = &(*current.array_it);
|
|
||||||
++stack.back().array_it;
|
|
||||||
write_msgpack_value_or_push(*child, stack);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const auto& object = *current.value->m_data.m_value.object;
|
|
||||||
if (current.object_it == object.cend())
|
|
||||||
{
|
|
||||||
stack.pop_back();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_handler == error_handler_t::strict)
|
|
||||||
{
|
|
||||||
check_utf8(current.object_it->first, *current.value);
|
|
||||||
}
|
|
||||||
write_msgpack(current.object_it->first);
|
|
||||||
const BasicJsonType* child = &(current.object_it->second);
|
|
||||||
++stack.back().object_it;
|
|
||||||
write_msgpack_value_or_push(*child, stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @return true when a closing ']' still has to be written after the elements
|
|
||||||
bool write_ubjson_start_array(const BasicJsonType& j, const bool use_count, const bool use_type,
|
|
||||||
const bool add_prefix, const bool use_bjdata, bool& prefix_required)
|
|
||||||
{
|
|
||||||
prefix_required = true;
|
|
||||||
if (add_prefix)
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type('['));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (use_type && !j.m_data.m_value.array->empty())
|
|
||||||
{
|
|
||||||
if (!use_count)
|
|
||||||
{
|
|
||||||
JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j));
|
|
||||||
}
|
|
||||||
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
|
|
||||||
const bool same_prefix = std::all_of(j.begin() + 1, j.end(),
|
|
||||||
[this, first_prefix, use_bjdata](const BasicJsonType & v)
|
|
||||||
{
|
|
||||||
return ubjson_prefix(v, use_bjdata) == first_prefix;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// those, at one byte per element, so the result can be read back.
|
|
||||||
// Objects are not affected: every element is preceded by its key.
|
|
||||||
const bool valueless_type = (first_prefix == 'Z' || first_prefix == 'T' || first_prefix == 'F');
|
|
||||||
const bool excessive_valueless = valueless_type
|
|
||||||
&& 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)))
|
|
||||||
{
|
|
||||||
prefix_required = false;
|
|
||||||
oa.write_character(to_char_type('$'));
|
|
||||||
oa.write_character(first_prefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (use_count)
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type('#'));
|
|
||||||
write_number_with_ubjson_prefix(j.m_data.m_value.array->size(), true, use_bjdata);
|
|
||||||
}
|
|
||||||
|
|
||||||
return !use_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @return true when a closing '}' still has to be written after the elements
|
|
||||||
bool write_ubjson_start_object(const BasicJsonType& j, const bool use_count, const bool use_type,
|
|
||||||
const bool add_prefix, const bool use_bjdata, bool& prefix_required)
|
|
||||||
{
|
|
||||||
prefix_required = true;
|
|
||||||
if (add_prefix)
|
if (add_prefix)
|
||||||
{
|
{
|
||||||
oa.write_character(to_char_type('{'));
|
oa.write_character(to_char_type('{'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool prefix_required = true;
|
||||||
if (use_type && !j.m_data.m_value.object->empty())
|
if (use_type && !j.m_data.m_value.object->empty())
|
||||||
{
|
{
|
||||||
if (!use_count)
|
if (!use_count)
|
||||||
@@ -1205,181 +880,47 @@ class binary_writer
|
|||||||
write_number_with_ubjson_prefix(j.m_data.m_value.object->size(), true, use_bjdata);
|
write_number_with_ubjson_prefix(j.m_data.m_value.object->size(), true, use_bjdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
return !use_count;
|
for (const auto& el : *j.m_data.m_value.object)
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief whether @a j is a BJData ND-array annotation object
|
|
||||||
(https://github.com/NeuroJSON/jdata)
|
|
||||||
|
|
||||||
Used by both the recursive object case of @ref write_ubjson and
|
|
||||||
@ref write_ubjson_value_or_push, which must agree on what counts as an
|
|
||||||
ND-array: @a j is only actually written as one once @ref
|
|
||||||
write_bjdata_ndarray has also accepted its contents.
|
|
||||||
|
|
||||||
@pre @a j.is_object()
|
|
||||||
*/
|
|
||||||
static bool is_bjdata_ndarray(const BasicJsonType& j)
|
|
||||||
{
|
{
|
||||||
const auto& object = *j.m_data.m_value.object;
|
|
||||||
return object.size() == 3
|
|
||||||
&& object.find("_ArrayType_") != object.end()
|
|
||||||
&& object.find("_ArraySize_") != object.end()
|
|
||||||
&& object.find("_ArrayData_") != object.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief an object or array @ref write_ubjson_iterative is still writing
|
|
||||||
/// the elements of
|
|
||||||
struct ubjson_frame
|
|
||||||
{
|
|
||||||
ubjson_frame(const BasicJsonType* value_, const bool prefix_required_) noexcept
|
|
||||||
: value(value_)
|
|
||||||
, prefix_required(prefix_required_)
|
|
||||||
{
|
|
||||||
if (value->is_object())
|
|
||||||
{
|
|
||||||
object_it = value->m_data.m_value.object->cbegin();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
array_it = value->m_data.m_value.array->cbegin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// declared for GCC's -Weffc++, which asks for them in a class with
|
|
||||||
// pointer members and a non-trivial destructor; the exception
|
|
||||||
// specifications are left implicit, as GCC 4.8 rejects explicit ones
|
|
||||||
// that differ from them
|
|
||||||
ubjson_frame(const ubjson_frame&) = default;
|
|
||||||
ubjson_frame(ubjson_frame&&) = default;
|
|
||||||
ubjson_frame& operator=(const ubjson_frame&) = default;
|
|
||||||
ubjson_frame& operator=(ubjson_frame&&) = default;
|
|
||||||
~ubjson_frame() = default;
|
|
||||||
|
|
||||||
/// the array or object being written
|
|
||||||
const BasicJsonType* value;
|
|
||||||
/// whether value's elements each carry their own type marker; an
|
|
||||||
/// optimized ($type) container writes it once for all of them instead
|
|
||||||
bool prefix_required;
|
|
||||||
typename BasicJsonType::object_t::const_iterator object_it{};
|
|
||||||
typename BasicJsonType::array_t::const_iterator array_it{};
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief write @a j with @ref write_ubjson, or write its header and push a
|
|
||||||
frame for @ref write_ubjson_iterative to continue with its elements
|
|
||||||
|
|
||||||
@param[in] add_prefix whether @a j's own type marker is written now (the
|
|
||||||
elements of an optimized container, and everything below the
|
|
||||||
top level, never repeat it)
|
|
||||||
|
|
||||||
@sa @ref write_cbor_value_or_push
|
|
||||||
*/
|
|
||||||
void write_ubjson_value_or_push(const BasicJsonType& j, const bool add_prefix, const bool use_count,
|
|
||||||
const bool use_type, const bool use_bjdata, const bjdata_version_t bjdata_version,
|
|
||||||
std::vector<ubjson_frame>& stack)
|
|
||||||
{
|
|
||||||
if (!j.is_array() && !j.is_object())
|
|
||||||
{
|
|
||||||
write_ubjson(j, use_count, use_type, add_prefix, use_bjdata, bjdata_version);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (use_bjdata && j.is_object() && is_bjdata_ndarray(j)
|
|
||||||
&& !write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type, bjdata_version))
|
|
||||||
{
|
|
||||||
// fully written as an ND-array: nothing below it to come back to
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool is_array = j.is_array();
|
|
||||||
bool prefix_required = true;
|
|
||||||
if (is_array)
|
|
||||||
{
|
|
||||||
write_ubjson_start_array(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_ubjson_start_object(j, use_count, use_type, add_prefix, use_bjdata, prefix_required);
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool empty = is_array ? j.m_data.m_value.array->empty() : j.m_data.m_value.object->empty();
|
|
||||||
if (!empty)
|
|
||||||
{
|
|
||||||
stack.emplace_back(&j, prefix_required);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// write_ubjson_start_array/_object return !use_count, i.e. whether a
|
|
||||||
// closer still has to be written; use_count is constant for the whole
|
|
||||||
// document, so that is recomputed here instead of being carried along
|
|
||||||
if (!use_count)
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(is_array ? ']' : '}'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief write out @a root and everything below it without the call stack
|
|
||||||
|
|
||||||
@sa @ref write_cbor_iterative
|
|
||||||
*/
|
|
||||||
void write_ubjson_iterative(const BasicJsonType& root, const bool use_count, const bool use_type,
|
|
||||||
const bool add_prefix, const bool use_bjdata, const bjdata_version_t bjdata_version)
|
|
||||||
{
|
|
||||||
std::vector<ubjson_frame> stack;
|
|
||||||
write_ubjson_value_or_push(root, add_prefix, use_count, use_type, use_bjdata, bjdata_version, stack);
|
|
||||||
|
|
||||||
while (!stack.empty())
|
|
||||||
{
|
|
||||||
const ubjson_frame current = stack.back();
|
|
||||||
const BasicJsonType& j = *current.value;
|
|
||||||
|
|
||||||
if (j.is_array())
|
|
||||||
{
|
|
||||||
const auto& array = *j.m_data.m_value.array;
|
|
||||||
if (current.array_it == array.cend())
|
|
||||||
{
|
|
||||||
if (!use_count)
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(']'));
|
|
||||||
}
|
|
||||||
stack.pop_back();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BasicJsonType* child = &(*current.array_it);
|
|
||||||
const bool child_prefix = current.prefix_required;
|
|
||||||
++stack.back().array_it;
|
|
||||||
write_ubjson_value_or_push(*child, child_prefix, use_count, use_type, use_bjdata, bjdata_version, stack);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const auto& object = *j.m_data.m_value.object;
|
|
||||||
if (current.object_it == object.cend())
|
|
||||||
{
|
|
||||||
if (!use_count)
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type('}'));
|
|
||||||
}
|
|
||||||
stack.pop_back();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
string_t storage;
|
string_t storage;
|
||||||
const string_t& key = sanitize_utf8_for_write(current.object_it->first, j, storage);
|
const string_t& key = sanitize_utf8_for_write(el.first, j, storage);
|
||||||
write_number_with_ubjson_prefix(key.size(), true, use_bjdata);
|
write_number_with_ubjson_prefix(key.size(), true, use_bjdata);
|
||||||
oa.write_characters(
|
oa.write_characters(
|
||||||
reinterpret_cast<const CharType*>(key.data()),
|
reinterpret_cast<const CharType*>(key.data()),
|
||||||
key.size());
|
key.size());
|
||||||
const BasicJsonType* child = &(current.object_it->second);
|
write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version);
|
||||||
const bool child_prefix = current.prefix_required;
|
|
||||||
++stack.back().object_it;
|
|
||||||
write_ubjson_value_or_push(*child, child_prefix, use_count, use_type, use_bjdata, bjdata_version, stack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!use_count)
|
||||||
|
{
|
||||||
|
oa.write_character(to_char_type('}'));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case value_t::discarded:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@param[in] j JSON value to serialize
|
||||||
|
*/
|
||||||
|
void write_bon8(const BasicJsonType& j)
|
||||||
|
{
|
||||||
|
bool string_open = false;
|
||||||
|
write_bon8_value(j, string_open);
|
||||||
|
|
||||||
|
// the last string of a message must be terminated
|
||||||
|
if (string_open)
|
||||||
|
{
|
||||||
|
oa.write_character(to_char_type(0xFF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
//////////
|
//////////
|
||||||
// BSON //
|
// BSON //
|
||||||
//////////
|
//////////
|
||||||
@@ -1631,7 +1172,6 @@ class binary_writer
|
|||||||
into a byte, before anything is written
|
into a byte, before anything is written
|
||||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||||
anything is written
|
anything is written
|
||||||
@throw type_error.321 if @a j is discarded
|
|
||||||
*/
|
*/
|
||||||
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
@@ -1658,12 +1198,10 @@ class binary_writer
|
|||||||
case value_t::null:
|
case value_t::null:
|
||||||
return 0ul;
|
return 0ul;
|
||||||
|
|
||||||
case value_t::discarded:
|
|
||||||
throw_on_discarded(j, "BSON");
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
return 0ul;
|
return 0ul;
|
||||||
@@ -1700,12 +1238,10 @@ class binary_writer
|
|||||||
case value_t::null:
|
case value_t::null:
|
||||||
return write_bson_null(name);
|
return write_bson_null(name);
|
||||||
|
|
||||||
case value_t::discarded:
|
|
||||||
throw_on_discarded(j, "BSON");
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
return;
|
return;
|
||||||
@@ -1772,8 +1308,6 @@ class binary_writer
|
|||||||
byte, before anything is written
|
byte, before anything is written
|
||||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||||
before anything is written
|
before anything is written
|
||||||
@throw type_error.321 if a value nested in @a document is discarded,
|
|
||||||
before anything is written
|
|
||||||
*/
|
*/
|
||||||
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
||||||
{
|
{
|
||||||
@@ -2283,31 +1817,16 @@ class binary_writer
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief validate (dry_run) or write one BJData ND-array element of dtype 'd' (single precision)
|
@brief validate (dry_run) or write one BJData ND-array element of dtype 'd' (single precision)
|
||||||
@return whether @a el's value survives narrowing to float and back without any change
|
@return whether @a el's value fits a float without overflow; always true when @a dry_run is false
|
||||||
(so the ND-array round-trips exactly), or is infinite or NaN; always true when
|
|
||||||
@a dry_run is false
|
|
||||||
*/
|
*/
|
||||||
bool write_bjdata_ndarray_float_element(const BasicJsonType& el, const bool dry_run)
|
bool write_bjdata_ndarray_float_element(const BasicJsonType& el, const bool dry_run)
|
||||||
{
|
{
|
||||||
const auto dval = el.template get<double>();
|
const auto dval = el.template get<double>();
|
||||||
if (dry_run)
|
if (dry_run)
|
||||||
{
|
{
|
||||||
#ifdef __GNUC__
|
return !std::isfinite(dval) ||
|
||||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
|
||||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
|
||||||
#endif
|
|
||||||
// a value that would be rounded (rather than exactly represented) by the
|
|
||||||
// narrowing to float is treated like an out-of-range integer element; this
|
|
||||||
// is the same criterion write_compact_float() uses for CBOR/MessagePack
|
|
||||||
const bool in_range = std::isnan(dval) ||
|
|
||||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
dval <= static_cast<double>((std::numeric_limits<float>::max)()));
|
||||||
static_cast<double>(static_cast<float>(dval)) == dval) ||
|
|
||||||
std::isinf(dval);
|
|
||||||
#ifdef __GNUC__
|
|
||||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
|
||||||
#endif
|
|
||||||
return in_range;
|
|
||||||
}
|
}
|
||||||
write_number(static_cast<float>(dval), true);
|
write_number(static_cast<float>(dval), true);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -164,12 +164,14 @@ class builder
|
|||||||
if (p[1] == '/')
|
if (p[1] == '/')
|
||||||
{
|
{
|
||||||
p += 2;
|
p += 2;
|
||||||
// (as in parse(), a null byte is the end of the input, so it is
|
|
||||||
// left for the caller to see)
|
|
||||||
while (p != e && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
|
while (p != e && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
|
if (NulIsEnd && p != e && *p == 0)
|
||||||
|
{
|
||||||
|
++p; // as in parse(), a null byte ends the comment like a line break
|
||||||
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
if (p[1] == '*')
|
if (p[1] == '*')
|
||||||
|
|||||||
@@ -31,7 +31,11 @@ namespace view
|
|||||||
{
|
{
|
||||||
|
|
||||||
/// append-only output buffer: writes through a raw pointer into a string that
|
/// append-only output buffer: writes through a raw pointer into a string that
|
||||||
/// is resized ahead, and trimmed by finish()
|
/// is resized ahead, and trimmed by finish(). The estimate is reserved, and the
|
||||||
|
/// string grows in steps of 64 KiB within it: resize() fills the new bytes
|
||||||
|
/// with zeros (before C++23, a string cannot grow without), and a small step
|
||||||
|
/// is filled while the writer is about to use it, in the cache, instead of
|
||||||
|
/// filling the whole estimate in memory first.
|
||||||
template<typename StringType>
|
template<typename StringType>
|
||||||
class output_buffer
|
class output_buffer
|
||||||
{
|
{
|
||||||
@@ -75,17 +79,49 @@ class output_buffer
|
|||||||
m_pos += n;
|
m_pos += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the write position and the end of the writable space, for a writer
|
||||||
|
/// that keeps the position in a local variable (set_cursor() hands it back)
|
||||||
|
char* cursor() const noexcept
|
||||||
|
{
|
||||||
|
return m_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* limit() const noexcept
|
||||||
|
{
|
||||||
|
return m_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_cursor(char* p) noexcept
|
||||||
|
{
|
||||||
|
m_pos = p;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// the size of a growth step (a function: std::min() takes a reference,
|
||||||
|
/// which a static constexpr member does not have before C++17)
|
||||||
|
static constexpr std::size_t step() noexcept
|
||||||
|
{
|
||||||
|
return 65536;
|
||||||
|
}
|
||||||
|
|
||||||
static StringType& sized(StringType& out, std::size_t estimate)
|
static StringType& sized(StringType& out, std::size_t estimate)
|
||||||
{
|
{
|
||||||
out.resize((std::max)(estimate, static_cast<std::size_t>(64)));
|
out.reserve(estimate);
|
||||||
|
out.resize((std::min)((std::max)(estimate, static_cast<std::size_t>(64)), step()));
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
NLOHMANN_VIEW_NOINLINE void grow(std::size_t n)
|
NLOHMANN_VIEW_NOINLINE void grow(std::size_t n)
|
||||||
{
|
{
|
||||||
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
||||||
m_out.resize((std::max)(m_out.size() * 2, used + n + 256));
|
// (a step does not go beyond the reserved estimate, so that a good
|
||||||
|
// estimate is never copied to a larger allocation)
|
||||||
|
const std::size_t size = (std::max)((std::min)(m_out.size() + step(), m_out.capacity()), used + n + 256);
|
||||||
|
if (size > m_out.capacity())
|
||||||
|
{
|
||||||
|
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
||||||
|
}
|
||||||
|
m_out.resize(size);
|
||||||
m_pos = &m_out[0] + used;
|
m_pos = &m_out[0] + used;
|
||||||
m_end = &m_out[0] + m_out.size();
|
m_end = &m_out[0] + m_out.size();
|
||||||
}
|
}
|
||||||
@@ -95,6 +131,105 @@ class output_buffer
|
|||||||
char* m_end;
|
char* m_end;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The length of the run at s that dump() writes unchanged without
|
||||||
|
/// ensure_ascii: all bytes but quotes, backslashes, and control characters.
|
||||||
|
/// Unlike detail::string_bulk_run(), non-ASCII bytes are not validated: the
|
||||||
|
/// strings of a document are valid UTF-8 (a damaged image loaded with
|
||||||
|
/// image_check::bounds can have others, which are then written unchanged).
|
||||||
|
inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||||
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (; i + 8 <= n; i += 8)
|
||||||
|
{
|
||||||
|
const std::uint64_t v = read_eight_bytes(s + i);
|
||||||
|
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"'
|
||||||
|
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\'
|
||||||
|
const std::uint64_t stop = (((q - ones) & ~q) | ((b - ones) & ~b) | ((v - 0x2020202020202020ull) & ~v)) & high;
|
||||||
|
if (stop != 0)
|
||||||
|
{
|
||||||
|
// the lowest flagged byte is the first stop: borrows only flag bytes above a true one
|
||||||
|
return i + (static_cast<std::size_t>(count_trailing_zeros(stop)) / 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < n; ++i)
|
||||||
|
{
|
||||||
|
if (s[i] == '"' || s[i] == '\\' || s[i] < 0x20)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A stack that starts in a buffer of the caller (a local array) and moves to
|
||||||
|
/// the heap (a vector of the caller) only when that is full, so that dumps of
|
||||||
|
/// shallow documents need no allocation. The top is a pointer, as in
|
||||||
|
/// std::vector. The address of the stack never escapes (the growth gets the
|
||||||
|
/// vector and returns the new storage), so its pointers stay in registers.
|
||||||
|
template<typename T>
|
||||||
|
class small_stack
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
small_stack(T* buffer, std::size_t capacity, std::vector<T>& heap) noexcept
|
||||||
|
: m_begin(buffer), m_top(buffer), m_end(buffer + capacity), m_heap(&heap)
|
||||||
|
{}
|
||||||
|
small_stack(const small_stack&) = delete;
|
||||||
|
small_stack(small_stack&&) = delete;
|
||||||
|
small_stack& operator=(const small_stack&) = delete;
|
||||||
|
small_stack& operator=(small_stack&&) = delete;
|
||||||
|
~small_stack() = default;
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE void push_back(const T& x)
|
||||||
|
{
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY(m_top == m_end))
|
||||||
|
{
|
||||||
|
const std::size_t used = size();
|
||||||
|
const std::size_t capacity = 2 * static_cast<std::size_t>(m_end - m_begin);
|
||||||
|
m_begin = grow(*m_heap, m_begin, used, capacity);
|
||||||
|
m_top = m_begin + used;
|
||||||
|
m_end = m_begin + capacity;
|
||||||
|
}
|
||||||
|
*m_top++ = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE T& back() noexcept
|
||||||
|
{
|
||||||
|
return m_top[-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE void pop_back() noexcept
|
||||||
|
{
|
||||||
|
--m_top;
|
||||||
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE bool empty() const noexcept
|
||||||
|
{
|
||||||
|
return m_top == m_begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t size() const noexcept
|
||||||
|
{
|
||||||
|
return static_cast<std::size_t>(m_top - m_begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// the used entries moved to heap storage of the given capacity
|
||||||
|
NLOHMANN_VIEW_NOINLINE static T* grow(std::vector<T>& heap, const T* begin, std::size_t used, std::size_t capacity)
|
||||||
|
{
|
||||||
|
std::vector<T> bigger(capacity);
|
||||||
|
std::copy(begin, begin + used, bigger.begin());
|
||||||
|
heap.swap(bigger);
|
||||||
|
return heap.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
T* m_begin;
|
||||||
|
T* m_top;
|
||||||
|
T* m_end;
|
||||||
|
std::vector<T>* m_heap;
|
||||||
|
};
|
||||||
|
|
||||||
/// how the view's dump() writes a value
|
/// how the view's dump() writes a value
|
||||||
struct dump_style
|
struct dump_style
|
||||||
{
|
{
|
||||||
@@ -129,6 +264,18 @@ class view_serializer
|
|||||||
|
|
||||||
void dump(const node* root)
|
void dump(const node* root)
|
||||||
{
|
{
|
||||||
|
if (!m_style.pretty && !m_style.ensure_ascii)
|
||||||
|
{
|
||||||
|
if (m_style.source_numbers)
|
||||||
|
{
|
||||||
|
dump_compact<true>(root);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dump_compact<false>(root);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
struct frame
|
struct frame
|
||||||
{
|
{
|
||||||
const node* pos; ///< next element, or key of the next member
|
const node* pos; ///< next element, or key of the next member
|
||||||
@@ -136,7 +283,9 @@ class view_serializer
|
|||||||
bool object;
|
bool object;
|
||||||
bool first; ///< nothing written yet
|
bool first; ///< nothing written yet
|
||||||
};
|
};
|
||||||
std::vector<frame> stack;
|
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::vector<frame> heap;
|
||||||
|
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
|
||||||
const node* n = root;
|
const node* n = root;
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
@@ -207,6 +356,289 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/*!
|
||||||
|
@brief the compact output without ensure_ascii (the default dump())
|
||||||
|
|
||||||
|
The same walk as dump(), with the write position in a local variable
|
||||||
|
(stores through char pointers would otherwise force a reload of the
|
||||||
|
buffer's members after each one), and with strings and number tokens of
|
||||||
|
the source copied by fixed-size moves of 32 bytes where the source has
|
||||||
|
that many bytes left, instead of a library call per token. The buffer
|
||||||
|
keeps 64 bytes of slack for the overshoot.
|
||||||
|
*/
|
||||||
|
/// a string that is not a plain string of the source (decoded, or written
|
||||||
|
/// by an edit), without ensure_ascii: runs without characters to escape
|
||||||
|
/// are copied
|
||||||
|
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n)
|
||||||
|
{
|
||||||
|
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
m_out.put('"');
|
||||||
|
for (std::size_t i = 0; i < n.len;)
|
||||||
|
{
|
||||||
|
const std::size_t run = plain_output_run(s + i, n.len - i);
|
||||||
|
if (run != 0)
|
||||||
|
{
|
||||||
|
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
i += run;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
m_out.put('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the copies of dump_compact() that are not fixed-size moves (long
|
||||||
|
/// strings, or near the end of the source); out of line, so that the
|
||||||
|
/// compiler does not merge the fixed-size moves into this call
|
||||||
|
NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
std::memcpy(to, from, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool SourceNumbers>
|
||||||
|
void dump_compact(const node* root)
|
||||||
|
{
|
||||||
|
struct frame
|
||||||
|
{
|
||||||
|
const node* pos; ///< (editable documents) next element, or key of the next member
|
||||||
|
const node* end;
|
||||||
|
bool object;
|
||||||
|
};
|
||||||
|
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::vector<frame> heap;
|
||||||
|
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
|
||||||
|
const char* const src = m_doc.src;
|
||||||
|
const char* const src_end = src + m_doc.size;
|
||||||
|
char* w = m_out.cursor();
|
||||||
|
char* lim = m_out.limit();
|
||||||
|
// room for n bytes and the slack
|
||||||
|
const auto room = [&](std::size_t n)
|
||||||
|
{
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(lim - w) < n + 64))
|
||||||
|
{
|
||||||
|
m_out.set_cursor(w);
|
||||||
|
m_out.reserve(n + 64);
|
||||||
|
w = m_out.cursor();
|
||||||
|
lim = m_out.limit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// copy n bytes of the source (after room(n))
|
||||||
|
const auto copy = [&](const char* from, std::size_t n)
|
||||||
|
{
|
||||||
|
if (n <= 32 && src_end - from >= 32)
|
||||||
|
{
|
||||||
|
std::memcpy(w, from, 32);
|
||||||
|
}
|
||||||
|
else if (n <= 256 && src_end - from >= static_cast<std::ptrdiff_t>(n) + 32)
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < n; i += 32)
|
||||||
|
{
|
||||||
|
std::memcpy(w + i, from + i, 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
copy_long(w, from, n);
|
||||||
|
}
|
||||||
|
w += n;
|
||||||
|
};
|
||||||
|
// a literal of n bytes (after room(n))
|
||||||
|
const auto literal = [&](const char* text, std::size_t n)
|
||||||
|
{
|
||||||
|
std::memcpy(w, text, n);
|
||||||
|
w += n;
|
||||||
|
};
|
||||||
|
// a string that is not a plain string of the source (out of line, so
|
||||||
|
// that the cursor stays in a register here)
|
||||||
|
const auto escaped = [&](const node & n)
|
||||||
|
{
|
||||||
|
m_out.set_cursor(w);
|
||||||
|
write_decoded(n);
|
||||||
|
w = m_out.cursor();
|
||||||
|
lim = m_out.limit();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read-only documents: the elements of a container follow it in the
|
||||||
|
// node array, so the walk goes through the array in order, and a
|
||||||
|
// frame only needs the end of its container. Editable documents: the
|
||||||
|
// elements of a moved container live elsewhere, so a frame keeps the
|
||||||
|
// position of the next element (see navigation).
|
||||||
|
// The innermost open container is kept in registers (cur; end ==
|
||||||
|
// nullptr: none), the stack holds the ones around it.
|
||||||
|
frame cur{nullptr, nullptr, false};
|
||||||
|
const node* n = root;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
// write the value at n (read-only documents: and advance n)
|
||||||
|
bool opened = false;
|
||||||
|
switch (static_cast<value_t>(n->kind))
|
||||||
|
{
|
||||||
|
case value_t::string:
|
||||||
|
if ((n->flags & node_flags::storage) == 0)
|
||||||
|
{
|
||||||
|
room(n->len + 2);
|
||||||
|
*w++ = '"';
|
||||||
|
copy(src + n->off, n->len);
|
||||||
|
*w++ = '"';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
escaped(*n);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case value_t::number_integer:
|
||||||
|
case value_t::number_unsigned:
|
||||||
|
{
|
||||||
|
const std::uint32_t len = number_length(*n);
|
||||||
|
room(len);
|
||||||
|
if (Editable && (n->flags & node_flags::storage) != 0)
|
||||||
|
{
|
||||||
|
copy_long(w, m_doc.str(*n), len); // a canonical token written by an edit
|
||||||
|
w += len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const char* const token = src + n->off;
|
||||||
|
if (!SourceNumbers && NLOHMANN_VIEW_UNLIKELY(len == 2 && token[0] == '-' && token[1] == '0'))
|
||||||
|
{
|
||||||
|
*w++ = '0'; // parse() reads -0 as the integer 0
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
copy(token, len);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case value_t::number_float:
|
||||||
|
if (SourceNumbers && (n->flags & node_flags::storage) != node_flags::edited)
|
||||||
|
{
|
||||||
|
room(n->len);
|
||||||
|
copy(src + n->off, n->len);
|
||||||
|
}
|
||||||
|
else if (std::is_same<number_float_t, double>::value)
|
||||||
|
{
|
||||||
|
room(64);
|
||||||
|
w = write_double_at(w, *n);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_out.set_cursor(w);
|
||||||
|
write_float_node(*n);
|
||||||
|
w = m_out.cursor();
|
||||||
|
lim = m_out.limit();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case value_t::boolean:
|
||||||
|
room(8);
|
||||||
|
if ((n->flags & node_flags::is_true) != 0)
|
||||||
|
{
|
||||||
|
literal("true", 4);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
literal("false", 5);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case value_t::object:
|
||||||
|
case value_t::array:
|
||||||
|
{
|
||||||
|
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||||
|
room(8);
|
||||||
|
if (n->len == 0)
|
||||||
|
{
|
||||||
|
literal(object ? "{}" : "[]", 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*w++ = object ? '{' : '[';
|
||||||
|
stack.push_back(cur);
|
||||||
|
if (Editable)
|
||||||
|
{
|
||||||
|
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cur = frame{nullptr, n + n->next, object};
|
||||||
|
}
|
||||||
|
opened = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case value_t::null:
|
||||||
|
room(8);
|
||||||
|
literal("null", 4);
|
||||||
|
break;
|
||||||
|
case value_t::binary: // LCOV_EXCL_LINE (not in a document)
|
||||||
|
case value_t::discarded: // LCOV_EXCL_LINE
|
||||||
|
default: // LCOV_EXCL_LINE
|
||||||
|
break; // LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
|
if (!Editable)
|
||||||
|
{
|
||||||
|
++n; // the next node: the first element of an opened container, or the node after a scalar
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to the next value: close finished containers, then separate
|
||||||
|
// (a container just opened has an element)
|
||||||
|
if (!opened)
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (cur.end == nullptr)
|
||||||
|
{
|
||||||
|
m_out.set_cursor(w);
|
||||||
|
m_out.finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((Editable ? cur.pos : n) != cur.end)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
room(1);
|
||||||
|
*w++ = cur.object ? '}' : ']';
|
||||||
|
cur = stack.back();
|
||||||
|
stack.pop_back();
|
||||||
|
}
|
||||||
|
room(1);
|
||||||
|
*w++ = ',';
|
||||||
|
}
|
||||||
|
const node* const at = Editable ? cur.pos : n;
|
||||||
|
if (cur.object)
|
||||||
|
{
|
||||||
|
const node& key = *at;
|
||||||
|
if ((key.flags & node_flags::storage) == 0)
|
||||||
|
{
|
||||||
|
room(key.len + 3);
|
||||||
|
*w++ = '"';
|
||||||
|
copy(src + key.off, key.len);
|
||||||
|
w[0] = '"';
|
||||||
|
w[1] = ':';
|
||||||
|
w += 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
escaped(key);
|
||||||
|
room(1);
|
||||||
|
*w++ = ':';
|
||||||
|
}
|
||||||
|
if (Editable)
|
||||||
|
{
|
||||||
|
n = nav::value(at + 1);
|
||||||
|
cur.pos = document_data::after(at + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Editable)
|
||||||
|
{
|
||||||
|
n = nav::value(at);
|
||||||
|
cur.pos = document_data::after(at);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void newline(std::size_t level)
|
void newline(std::size_t level)
|
||||||
{
|
{
|
||||||
if (m_style.pretty)
|
if (m_style.pretty)
|
||||||
@@ -258,7 +690,7 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
write_float(float_value<number_float_t>(m_doc, n));
|
write_float_node(n);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
|
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
|
||||||
@@ -270,6 +702,83 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// a float node as dump() writes it
|
||||||
|
void write_float_node(const node& n)
|
||||||
|
{
|
||||||
|
write_float_node(n, std::is_same<number_float_t, double> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_float_node(const node& n, std::false_type /*other*/)
|
||||||
|
{
|
||||||
|
write_float(float_value<number_float_t>(m_doc, n));
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_float_node(const node& n, std::true_type /*double*/)
|
||||||
|
{
|
||||||
|
m_out.reserve(64);
|
||||||
|
m_out.set_cursor(write_double_at(m_out.cursor(), n));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief (doubles) the float at n as dump() writes it, at w (64 bytes of room)
|
||||||
|
|
||||||
|
A token of at most 15 significant digits is written from its digits,
|
||||||
|
without a conversion: two decimals of at most 15 digits are farther
|
||||||
|
apart than the rounding interval of a (normal) double (the argument
|
||||||
|
behind DBL_DIG), so the token's digits are the shortest ones of its
|
||||||
|
double, which the library's conversion writes (Zmij). Other tokens are
|
||||||
|
converted from the digits already read.
|
||||||
|
*/
|
||||||
|
char* write_double_at(char* w, const node& n)
|
||||||
|
{
|
||||||
|
const unsigned int_digits = n.extra & 0xFFu;
|
||||||
|
const unsigned frac_digits = n.extra >> 8u;
|
||||||
|
if ((n.flags & node_flags::storage) != node_flags::edited && int_digits + frac_digits <= 19)
|
||||||
|
{
|
||||||
|
const auto* const first = reinterpret_cast<const unsigned char*>(m_doc.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
const float_significand d = layout_decimal(first, first + n.len, int_digits, frac_digits, reinterpret_cast<const unsigned char*>(m_doc.src + m_doc.size)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
// (the exponent keeps the value far from subnormals and overflow)
|
||||||
|
if (d.w != 0 && d.w < 1000000000000000u && d.exponent >= -290 && d.exponent <= 290)
|
||||||
|
{
|
||||||
|
*w = '-';
|
||||||
|
w += d.negative ? 1 : 0;
|
||||||
|
// (without leading zeros, all digits of the token count)
|
||||||
|
const unsigned char lead = first[d.negative ? 1 : 0];
|
||||||
|
return lead != '0' ? ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(int_digits + frac_digits), static_cast<int>(d.exponent))
|
||||||
|
: ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(d.exponent));
|
||||||
|
}
|
||||||
|
return write_double_value_at(w, decimal_to_float<double>(d)); // (without reading the token again)
|
||||||
|
}
|
||||||
|
return write_double_value_at(w, static_cast<double>(float_value<number_float_t>(m_doc, n)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// n bytes of text at w
|
||||||
|
static char* write_text_at(char* w, const char* text, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
std::memcpy(w, text, n);
|
||||||
|
return w + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// a double as dump() writes it, at w (64 bytes of room)
|
||||||
|
static char* write_double_value_at(char* w, double x)
|
||||||
|
{
|
||||||
|
// (from the bits: without the checks of to_chars())
|
||||||
|
std::uint64_t bits = 0;
|
||||||
|
std::memcpy(&bits, &x, sizeof(bits));
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
|
||||||
|
{
|
||||||
|
return write_text_at(w, "null", 4);
|
||||||
|
}
|
||||||
|
*w = '-';
|
||||||
|
w += bits >> 63u;
|
||||||
|
bits &= ~(std::uint64_t{1} << 63u);
|
||||||
|
if (bits == 0)
|
||||||
|
{
|
||||||
|
return write_text_at(w, "0.0", 3);
|
||||||
|
}
|
||||||
|
return ::nlohmann::detail::dtoa_impl::write_shortest(w, ::nlohmann::detail::zmij::to_shortest(bits));
|
||||||
|
}
|
||||||
|
|
||||||
/// as serializer::dump_float()
|
/// as serializer::dump_float()
|
||||||
void write_float(number_float_t x)
|
void write_float(number_float_t x)
|
||||||
{
|
{
|
||||||
|
|||||||
+100
-474
File diff suppressed because it is too large.
Load diff
@@ -592,8 +592,10 @@ class basic_json_view
|
|||||||
style.indent_char = indent_char;
|
style.indent_char = indent_char;
|
||||||
style.ensure_ascii = ensure_ascii;
|
style.ensure_ascii = ensure_ascii;
|
||||||
style.source_numbers = numbers == number_format::source;
|
style.source_numbers = numbers == number_format::source;
|
||||||
// the compact text is about as long as the source text of the value
|
// the compact text is about as long as the source text of the value;
|
||||||
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 64;
|
// the compact writer keeps 64 bytes of slack, so that it does not grow
|
||||||
|
// the buffer just before the end
|
||||||
|
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
|
||||||
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,9 +259,6 @@ public:
|
|||||||
|
|
||||||
for (auto it = first; std::next(it, elements_affected) != Container::end(); ++it)
|
for (auto it = first; std::next(it, elements_affected) != Container::end(); ++it)
|
||||||
{
|
{
|
||||||
// false positive: Infer's model of std::string keeps the buffer of a
|
|
||||||
// moved-from string, so it assumes a buffer is destroyed twice
|
|
||||||
// @infer-ignore USE_AFTER_DELETE
|
|
||||||
it->~value_type(); // destroy but keep allocation
|
it->~value_type(); // destroy but keep allocation
|
||||||
new (&*it) value_type{std::move(*std::next(it, elements_affected))}; // "move" next element to it
|
new (&*it) value_type{std::move(*std::next(it, elements_affected))}; // "move" next element to it
|
||||||
}
|
}
|
||||||
|
|||||||
-30252
File diff suppressed because it is too large.
Load diff
+623
-1754
File diff suppressed because it is too large.
Load diff
@@ -67,10 +67,6 @@
|
|||||||
#define JSON_STRICT_BINARY_UTF8 0
|
#define JSON_STRICT_BINARY_UTF8 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||||
#else
|
#else
|
||||||
@@ -113,20 +109,14 @@
|
|||||||
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
|
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS _ekmo
|
|
||||||
#else
|
|
||||||
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||||
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Construct the namespace ABI tags component
|
// Construct the namespace ABI tags component
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h) json_abi ## a ## b ## c ## d ## e ## f ## g ## h
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) json_abi ## a ## b ## c ## d ## e ## f ## g
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h) \
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g) \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h)
|
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g)
|
||||||
|
|
||||||
#define NLOHMANN_JSON_ABI_TAGS \
|
#define NLOHMANN_JSON_ABI_TAGS \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||||
@@ -136,8 +126,7 @@
|
|||||||
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
|
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
|
||||||
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
||||||
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
|
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
|
||||||
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
|
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8)
|
||||||
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS)
|
|
||||||
|
|
||||||
// Construct the namespace version component
|
// Construct the namespace version component
|
||||||
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||||
|
|||||||
@@ -1218,12 +1218,14 @@ class builder
|
|||||||
if (p[1] == '/')
|
if (p[1] == '/')
|
||||||
{
|
{
|
||||||
p += 2;
|
p += 2;
|
||||||
// (as in parse(), a null byte is the end of the input, so it is
|
|
||||||
// left for the caller to see)
|
|
||||||
while (p != e && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
|
while (p != e && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
|
if (NulIsEnd && p != e && *p == 0)
|
||||||
|
{
|
||||||
|
++p; // as in parse(), a null byte ends the comment like a line break
|
||||||
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
if (p[1] == '*')
|
if (p[1] == '*')
|
||||||
@@ -5407,7 +5409,11 @@ namespace view
|
|||||||
{
|
{
|
||||||
|
|
||||||
/// append-only output buffer: writes through a raw pointer into a string that
|
/// append-only output buffer: writes through a raw pointer into a string that
|
||||||
/// is resized ahead, and trimmed by finish()
|
/// is resized ahead, and trimmed by finish(). The estimate is reserved, and the
|
||||||
|
/// string grows in steps of 64 KiB within it: resize() fills the new bytes
|
||||||
|
/// with zeros (before C++23, a string cannot grow without), and a small step
|
||||||
|
/// is filled while the writer is about to use it, in the cache, instead of
|
||||||
|
/// filling the whole estimate in memory first.
|
||||||
template<typename StringType>
|
template<typename StringType>
|
||||||
class output_buffer
|
class output_buffer
|
||||||
{
|
{
|
||||||
@@ -5451,17 +5457,49 @@ class output_buffer
|
|||||||
m_pos += n;
|
m_pos += n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the write position and the end of the writable space, for a writer
|
||||||
|
/// that keeps the position in a local variable (set_cursor() hands it back)
|
||||||
|
char* cursor() const noexcept
|
||||||
|
{
|
||||||
|
return m_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* limit() const noexcept
|
||||||
|
{
|
||||||
|
return m_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_cursor(char* p) noexcept
|
||||||
|
{
|
||||||
|
m_pos = p;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// the size of a growth step (a function: std::min() takes a reference,
|
||||||
|
/// which a static constexpr member does not have before C++17)
|
||||||
|
static constexpr std::size_t step() noexcept
|
||||||
|
{
|
||||||
|
return 65536;
|
||||||
|
}
|
||||||
|
|
||||||
static StringType& sized(StringType& out, std::size_t estimate)
|
static StringType& sized(StringType& out, std::size_t estimate)
|
||||||
{
|
{
|
||||||
out.resize((std::max)(estimate, static_cast<std::size_t>(64)));
|
out.reserve(estimate);
|
||||||
|
out.resize((std::min)((std::max)(estimate, static_cast<std::size_t>(64)), step()));
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
NLOHMANN_VIEW_NOINLINE void grow(std::size_t n)
|
NLOHMANN_VIEW_NOINLINE void grow(std::size_t n)
|
||||||
{
|
{
|
||||||
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
||||||
m_out.resize((std::max)(m_out.size() * 2, used + n + 256));
|
// (a step does not go beyond the reserved estimate, so that a good
|
||||||
|
// estimate is never copied to a larger allocation)
|
||||||
|
const std::size_t size = (std::max)((std::min)(m_out.size() + step(), m_out.capacity()), used + n + 256);
|
||||||
|
if (size > m_out.capacity())
|
||||||
|
{
|
||||||
|
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
||||||
|
}
|
||||||
|
m_out.resize(size);
|
||||||
m_pos = &m_out[0] + used;
|
m_pos = &m_out[0] + used;
|
||||||
m_end = &m_out[0] + m_out.size();
|
m_end = &m_out[0] + m_out.size();
|
||||||
}
|
}
|
||||||
@@ -5471,6 +5509,105 @@ class output_buffer
|
|||||||
char* m_end;
|
char* m_end;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The length of the run at s that dump() writes unchanged without
|
||||||
|
/// ensure_ascii: all bytes but quotes, backslashes, and control characters.
|
||||||
|
/// Unlike detail::string_bulk_run(), non-ASCII bytes are not validated: the
|
||||||
|
/// strings of a document are valid UTF-8 (a damaged image loaded with
|
||||||
|
/// image_check::bounds can have others, which are then written unchanged).
|
||||||
|
inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||||
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (; i + 8 <= n; i += 8)
|
||||||
|
{
|
||||||
|
const std::uint64_t v = read_eight_bytes(s + i);
|
||||||
|
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"'
|
||||||
|
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\'
|
||||||
|
const std::uint64_t stop = (((q - ones) & ~q) | ((b - ones) & ~b) | ((v - 0x2020202020202020ull) & ~v)) & high;
|
||||||
|
if (stop != 0)
|
||||||
|
{
|
||||||
|
// the lowest flagged byte is the first stop: borrows only flag bytes above a true one
|
||||||
|
return i + (static_cast<std::size_t>(count_trailing_zeros(stop)) / 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < n; ++i)
|
||||||
|
{
|
||||||
|
if (s[i] == '"' || s[i] == '\\' || s[i] < 0x20)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A stack that starts in a buffer of the caller (a local array) and moves to
|
||||||
|
/// the heap (a vector of the caller) only when that is full, so that dumps of
|
||||||
|
/// shallow documents need no allocation. The top is a pointer, as in
|
||||||
|
/// std::vector. The address of the stack never escapes (the growth gets the
|
||||||
|
/// vector and returns the new storage), so its pointers stay in registers.
|
||||||
|
template<typename T>
|
||||||
|
class small_stack
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
small_stack(T* buffer, std::size_t capacity, std::vector<T>& heap) noexcept
|
||||||
|
: m_begin(buffer), m_top(buffer), m_end(buffer + capacity), m_heap(&heap)
|
||||||
|
{}
|
||||||
|
small_stack(const small_stack&) = delete;
|
||||||
|
small_stack(small_stack&&) = delete;
|
||||||
|
small_stack& operator=(const small_stack&) = delete;
|
||||||
|
small_stack& operator=(small_stack&&) = delete;
|
||||||
|
~small_stack() = default;
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE void push_back(const T& x)
|
||||||
|
{
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY(m_top == m_end))
|
||||||
|
{
|
||||||
|
const std::size_t used = size();
|
||||||
|
const std::size_t capacity = 2 * static_cast<std::size_t>(m_end - m_begin);
|
||||||
|
m_begin = grow(*m_heap, m_begin, used, capacity);
|
||||||
|
m_top = m_begin + used;
|
||||||
|
m_end = m_begin + capacity;
|
||||||
|
}
|
||||||
|
*m_top++ = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE T& back() noexcept
|
||||||
|
{
|
||||||
|
return m_top[-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE void pop_back() noexcept
|
||||||
|
{
|
||||||
|
--m_top;
|
||||||
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE bool empty() const noexcept
|
||||||
|
{
|
||||||
|
return m_top == m_begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t size() const noexcept
|
||||||
|
{
|
||||||
|
return static_cast<std::size_t>(m_top - m_begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// the used entries moved to heap storage of the given capacity
|
||||||
|
NLOHMANN_VIEW_NOINLINE static T* grow(std::vector<T>& heap, const T* begin, std::size_t used, std::size_t capacity)
|
||||||
|
{
|
||||||
|
std::vector<T> bigger(capacity);
|
||||||
|
std::copy(begin, begin + used, bigger.begin());
|
||||||
|
heap.swap(bigger);
|
||||||
|
return heap.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
T* m_begin;
|
||||||
|
T* m_top;
|
||||||
|
T* m_end;
|
||||||
|
std::vector<T>* m_heap;
|
||||||
|
};
|
||||||
|
|
||||||
/// how the view's dump() writes a value
|
/// how the view's dump() writes a value
|
||||||
struct dump_style
|
struct dump_style
|
||||||
{
|
{
|
||||||
@@ -5505,6 +5642,18 @@ class view_serializer
|
|||||||
|
|
||||||
void dump(const node* root)
|
void dump(const node* root)
|
||||||
{
|
{
|
||||||
|
if (!m_style.pretty && !m_style.ensure_ascii)
|
||||||
|
{
|
||||||
|
if (m_style.source_numbers)
|
||||||
|
{
|
||||||
|
dump_compact<true>(root);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dump_compact<false>(root);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
struct frame
|
struct frame
|
||||||
{
|
{
|
||||||
const node* pos; ///< next element, or key of the next member
|
const node* pos; ///< next element, or key of the next member
|
||||||
@@ -5512,7 +5661,9 @@ class view_serializer
|
|||||||
bool object;
|
bool object;
|
||||||
bool first; ///< nothing written yet
|
bool first; ///< nothing written yet
|
||||||
};
|
};
|
||||||
std::vector<frame> stack;
|
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::vector<frame> heap;
|
||||||
|
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
|
||||||
const node* n = root;
|
const node* n = root;
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
@@ -5583,6 +5734,289 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/*!
|
||||||
|
@brief the compact output without ensure_ascii (the default dump())
|
||||||
|
|
||||||
|
The same walk as dump(), with the write position in a local variable
|
||||||
|
(stores through char pointers would otherwise force a reload of the
|
||||||
|
buffer's members after each one), and with strings and number tokens of
|
||||||
|
the source copied by fixed-size moves of 32 bytes where the source has
|
||||||
|
that many bytes left, instead of a library call per token. The buffer
|
||||||
|
keeps 64 bytes of slack for the overshoot.
|
||||||
|
*/
|
||||||
|
/// a string that is not a plain string of the source (decoded, or written
|
||||||
|
/// by an edit), without ensure_ascii: runs without characters to escape
|
||||||
|
/// are copied
|
||||||
|
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n)
|
||||||
|
{
|
||||||
|
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
m_out.put('"');
|
||||||
|
for (std::size_t i = 0; i < n.len;)
|
||||||
|
{
|
||||||
|
const std::size_t run = plain_output_run(s + i, n.len - i);
|
||||||
|
if (run != 0)
|
||||||
|
{
|
||||||
|
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
i += run;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
m_out.put('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the copies of dump_compact() that are not fixed-size moves (long
|
||||||
|
/// strings, or near the end of the source); out of line, so that the
|
||||||
|
/// compiler does not merge the fixed-size moves into this call
|
||||||
|
NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
std::memcpy(to, from, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool SourceNumbers>
|
||||||
|
void dump_compact(const node* root)
|
||||||
|
{
|
||||||
|
struct frame
|
||||||
|
{
|
||||||
|
const node* pos; ///< (editable documents) next element, or key of the next member
|
||||||
|
const node* end;
|
||||||
|
bool object;
|
||||||
|
};
|
||||||
|
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::vector<frame> heap;
|
||||||
|
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
|
||||||
|
const char* const src = m_doc.src;
|
||||||
|
const char* const src_end = src + m_doc.size;
|
||||||
|
char* w = m_out.cursor();
|
||||||
|
char* lim = m_out.limit();
|
||||||
|
// room for n bytes and the slack
|
||||||
|
const auto room = [&](std::size_t n)
|
||||||
|
{
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(lim - w) < n + 64))
|
||||||
|
{
|
||||||
|
m_out.set_cursor(w);
|
||||||
|
m_out.reserve(n + 64);
|
||||||
|
w = m_out.cursor();
|
||||||
|
lim = m_out.limit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// copy n bytes of the source (after room(n))
|
||||||
|
const auto copy = [&](const char* from, std::size_t n)
|
||||||
|
{
|
||||||
|
if (n <= 32 && src_end - from >= 32)
|
||||||
|
{
|
||||||
|
std::memcpy(w, from, 32);
|
||||||
|
}
|
||||||
|
else if (n <= 256 && src_end - from >= static_cast<std::ptrdiff_t>(n) + 32)
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < n; i += 32)
|
||||||
|
{
|
||||||
|
std::memcpy(w + i, from + i, 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
copy_long(w, from, n);
|
||||||
|
}
|
||||||
|
w += n;
|
||||||
|
};
|
||||||
|
// a literal of n bytes (after room(n))
|
||||||
|
const auto literal = [&](const char* text, std::size_t n)
|
||||||
|
{
|
||||||
|
std::memcpy(w, text, n);
|
||||||
|
w += n;
|
||||||
|
};
|
||||||
|
// a string that is not a plain string of the source (out of line, so
|
||||||
|
// that the cursor stays in a register here)
|
||||||
|
const auto escaped = [&](const node & n)
|
||||||
|
{
|
||||||
|
m_out.set_cursor(w);
|
||||||
|
write_decoded(n);
|
||||||
|
w = m_out.cursor();
|
||||||
|
lim = m_out.limit();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read-only documents: the elements of a container follow it in the
|
||||||
|
// node array, so the walk goes through the array in order, and a
|
||||||
|
// frame only needs the end of its container. Editable documents: the
|
||||||
|
// elements of a moved container live elsewhere, so a frame keeps the
|
||||||
|
// position of the next element (see navigation).
|
||||||
|
// The innermost open container is kept in registers (cur; end ==
|
||||||
|
// nullptr: none), the stack holds the ones around it.
|
||||||
|
frame cur{nullptr, nullptr, false};
|
||||||
|
const node* n = root;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
// write the value at n (read-only documents: and advance n)
|
||||||
|
bool opened = false;
|
||||||
|
switch (static_cast<value_t>(n->kind))
|
||||||
|
{
|
||||||
|
case value_t::string:
|
||||||
|
if ((n->flags & node_flags::storage) == 0)
|
||||||
|
{
|
||||||
|
room(n->len + 2);
|
||||||
|
*w++ = '"';
|
||||||
|
copy(src + n->off, n->len);
|
||||||
|
*w++ = '"';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
escaped(*n);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case value_t::number_integer:
|
||||||
|
case value_t::number_unsigned:
|
||||||
|
{
|
||||||
|
const std::uint32_t len = number_length(*n);
|
||||||
|
room(len);
|
||||||
|
if (Editable && (n->flags & node_flags::storage) != 0)
|
||||||
|
{
|
||||||
|
copy_long(w, m_doc.str(*n), len); // a canonical token written by an edit
|
||||||
|
w += len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const char* const token = src + n->off;
|
||||||
|
if (!SourceNumbers && NLOHMANN_VIEW_UNLIKELY(len == 2 && token[0] == '-' && token[1] == '0'))
|
||||||
|
{
|
||||||
|
*w++ = '0'; // parse() reads -0 as the integer 0
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
copy(token, len);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case value_t::number_float:
|
||||||
|
if (SourceNumbers && (n->flags & node_flags::storage) != node_flags::edited)
|
||||||
|
{
|
||||||
|
room(n->len);
|
||||||
|
copy(src + n->off, n->len);
|
||||||
|
}
|
||||||
|
else if (std::is_same<number_float_t, double>::value)
|
||||||
|
{
|
||||||
|
room(64);
|
||||||
|
w = write_double_at(w, *n);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_out.set_cursor(w);
|
||||||
|
write_float_node(*n);
|
||||||
|
w = m_out.cursor();
|
||||||
|
lim = m_out.limit();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case value_t::boolean:
|
||||||
|
room(8);
|
||||||
|
if ((n->flags & node_flags::is_true) != 0)
|
||||||
|
{
|
||||||
|
literal("true", 4);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
literal("false", 5);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case value_t::object:
|
||||||
|
case value_t::array:
|
||||||
|
{
|
||||||
|
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||||
|
room(8);
|
||||||
|
if (n->len == 0)
|
||||||
|
{
|
||||||
|
literal(object ? "{}" : "[]", 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*w++ = object ? '{' : '[';
|
||||||
|
stack.push_back(cur);
|
||||||
|
if (Editable)
|
||||||
|
{
|
||||||
|
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cur = frame{nullptr, n + n->next, object};
|
||||||
|
}
|
||||||
|
opened = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case value_t::null:
|
||||||
|
room(8);
|
||||||
|
literal("null", 4);
|
||||||
|
break;
|
||||||
|
case value_t::binary: // LCOV_EXCL_LINE (not in a document)
|
||||||
|
case value_t::discarded: // LCOV_EXCL_LINE
|
||||||
|
default: // LCOV_EXCL_LINE
|
||||||
|
break; // LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
|
if (!Editable)
|
||||||
|
{
|
||||||
|
++n; // the next node: the first element of an opened container, or the node after a scalar
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to the next value: close finished containers, then separate
|
||||||
|
// (a container just opened has an element)
|
||||||
|
if (!opened)
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (cur.end == nullptr)
|
||||||
|
{
|
||||||
|
m_out.set_cursor(w);
|
||||||
|
m_out.finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((Editable ? cur.pos : n) != cur.end)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
room(1);
|
||||||
|
*w++ = cur.object ? '}' : ']';
|
||||||
|
cur = stack.back();
|
||||||
|
stack.pop_back();
|
||||||
|
}
|
||||||
|
room(1);
|
||||||
|
*w++ = ',';
|
||||||
|
}
|
||||||
|
const node* const at = Editable ? cur.pos : n;
|
||||||
|
if (cur.object)
|
||||||
|
{
|
||||||
|
const node& key = *at;
|
||||||
|
if ((key.flags & node_flags::storage) == 0)
|
||||||
|
{
|
||||||
|
room(key.len + 3);
|
||||||
|
*w++ = '"';
|
||||||
|
copy(src + key.off, key.len);
|
||||||
|
w[0] = '"';
|
||||||
|
w[1] = ':';
|
||||||
|
w += 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
escaped(key);
|
||||||
|
room(1);
|
||||||
|
*w++ = ':';
|
||||||
|
}
|
||||||
|
if (Editable)
|
||||||
|
{
|
||||||
|
n = nav::value(at + 1);
|
||||||
|
cur.pos = document_data::after(at + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Editable)
|
||||||
|
{
|
||||||
|
n = nav::value(at);
|
||||||
|
cur.pos = document_data::after(at);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void newline(std::size_t level)
|
void newline(std::size_t level)
|
||||||
{
|
{
|
||||||
if (m_style.pretty)
|
if (m_style.pretty)
|
||||||
@@ -5634,7 +6068,7 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
write_float(float_value<number_float_t>(m_doc, n));
|
write_float_node(n);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
|
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
|
||||||
@@ -5646,6 +6080,83 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// a float node as dump() writes it
|
||||||
|
void write_float_node(const node& n)
|
||||||
|
{
|
||||||
|
write_float_node(n, std::is_same<number_float_t, double> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_float_node(const node& n, std::false_type /*other*/)
|
||||||
|
{
|
||||||
|
write_float(float_value<number_float_t>(m_doc, n));
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_float_node(const node& n, std::true_type /*double*/)
|
||||||
|
{
|
||||||
|
m_out.reserve(64);
|
||||||
|
m_out.set_cursor(write_double_at(m_out.cursor(), n));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief (doubles) the float at n as dump() writes it, at w (64 bytes of room)
|
||||||
|
|
||||||
|
A token of at most 15 significant digits is written from its digits,
|
||||||
|
without a conversion: two decimals of at most 15 digits are farther
|
||||||
|
apart than the rounding interval of a (normal) double (the argument
|
||||||
|
behind DBL_DIG), so the token's digits are the shortest ones of its
|
||||||
|
double, which the library's conversion writes (Zmij). Other tokens are
|
||||||
|
converted from the digits already read.
|
||||||
|
*/
|
||||||
|
char* write_double_at(char* w, const node& n)
|
||||||
|
{
|
||||||
|
const unsigned int_digits = n.extra & 0xFFu;
|
||||||
|
const unsigned frac_digits = n.extra >> 8u;
|
||||||
|
if ((n.flags & node_flags::storage) != node_flags::edited && int_digits + frac_digits <= 19)
|
||||||
|
{
|
||||||
|
const auto* const first = reinterpret_cast<const unsigned char*>(m_doc.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
const float_significand d = layout_decimal(first, first + n.len, int_digits, frac_digits, reinterpret_cast<const unsigned char*>(m_doc.src + m_doc.size)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
// (the exponent keeps the value far from subnormals and overflow)
|
||||||
|
if (d.w != 0 && d.w < 1000000000000000u && d.exponent >= -290 && d.exponent <= 290)
|
||||||
|
{
|
||||||
|
*w = '-';
|
||||||
|
w += d.negative ? 1 : 0;
|
||||||
|
// (without leading zeros, all digits of the token count)
|
||||||
|
const unsigned char lead = first[d.negative ? 1 : 0];
|
||||||
|
return lead != '0' ? ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(int_digits + frac_digits), static_cast<int>(d.exponent))
|
||||||
|
: ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(d.exponent));
|
||||||
|
}
|
||||||
|
return write_double_value_at(w, decimal_to_float<double>(d)); // (without reading the token again)
|
||||||
|
}
|
||||||
|
return write_double_value_at(w, static_cast<double>(float_value<number_float_t>(m_doc, n)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// n bytes of text at w
|
||||||
|
static char* write_text_at(char* w, const char* text, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
std::memcpy(w, text, n);
|
||||||
|
return w + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// a double as dump() writes it, at w (64 bytes of room)
|
||||||
|
static char* write_double_value_at(char* w, double x)
|
||||||
|
{
|
||||||
|
// (from the bits: without the checks of to_chars())
|
||||||
|
std::uint64_t bits = 0;
|
||||||
|
std::memcpy(&bits, &x, sizeof(bits));
|
||||||
|
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
|
||||||
|
{
|
||||||
|
return write_text_at(w, "null", 4);
|
||||||
|
}
|
||||||
|
*w = '-';
|
||||||
|
w += bits >> 63u;
|
||||||
|
bits &= ~(std::uint64_t{1} << 63u);
|
||||||
|
if (bits == 0)
|
||||||
|
{
|
||||||
|
return write_text_at(w, "0.0", 3);
|
||||||
|
}
|
||||||
|
return ::nlohmann::detail::dtoa_impl::write_shortest(w, ::nlohmann::detail::zmij::to_shortest(bits));
|
||||||
|
}
|
||||||
|
|
||||||
/// as serializer::dump_float()
|
/// as serializer::dump_float()
|
||||||
void write_float(number_float_t x)
|
void write_float(number_float_t x)
|
||||||
{
|
{
|
||||||
@@ -6564,8 +7075,10 @@ class basic_json_view
|
|||||||
style.indent_char = indent_char;
|
style.indent_char = indent_char;
|
||||||
style.ensure_ascii = ensure_ascii;
|
style.ensure_ascii = ensure_ascii;
|
||||||
style.source_numbers = numbers == number_format::source;
|
style.source_numbers = numbers == number_format::source;
|
||||||
// the compact text is about as long as the source text of the value
|
// the compact text is about as long as the source text of the value;
|
||||||
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 64;
|
// the compact writer keeps 64 bytes of slack, so that it does not grow
|
||||||
|
// the buffer just before the end
|
||||||
|
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
|
||||||
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-40
@@ -8,7 +8,6 @@ set(JSON_SIMDUTF_VERSION 9.1.0 CACHE STRING "The simdutf version used by JSON_Te
|
|||||||
|
|
||||||
set(JSON_32bitTest AUTO CACHE STRING "Enable the 32bit unit test (ON/OFF/AUTO/ONLY).")
|
set(JSON_32bitTest AUTO CACHE STRING "Enable the 32bit unit test (ON/OFF/AUTO/ONLY).")
|
||||||
set(JSON_TestStandards "" CACHE STRING "The list of standards to test explicitly.")
|
set(JSON_TestStandards "" CACHE STRING "The list of standards to test explicitly.")
|
||||||
set(JSON_TestShard "" CACHE STRING "Build only a part of the unit tests, given as <index>/<count> (e.g. 0/2), to split them across CI jobs with a time limit.")
|
|
||||||
|
|
||||||
# using an env var, since this will also affect targets executing cmake (such as "ci_test_compiler_default")
|
# using an env var, since this will also affect targets executing cmake (such as "ci_test_compiler_default")
|
||||||
if (NOT "" STREQUAL "$ENV{JSON_FORCED_GLOBAL_COMPILE_OPTIONS}")
|
if (NOT "" STREQUAL "$ENV{JSON_FORCED_GLOBAL_COMPILE_OPTIONS}")
|
||||||
@@ -135,17 +134,12 @@ json_test_set_test_options(test-disabled_exceptions
|
|||||||
#$<$<CXX_COMPILER_ID:MSVC>:/EH>
|
#$<$<CXX_COMPILER_ID:MSVC>:/EH>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# raise timeout of expensive Unicode test
|
||||||
|
json_test_set_test_options(test-unicode4 TEST_PROPERTIES TIMEOUT 3000)
|
||||||
|
|
||||||
# only the #972 regression test needs thirdparty/fifo_map on its include path
|
# only the #972 regression test needs thirdparty/fifo_map on its include path
|
||||||
json_test_set_test_options(test-regression1 LINK_LIBRARIES fifo_map_include)
|
json_test_set_test_options(test-regression1 LINK_LIBRARIES fifo_map_include)
|
||||||
|
|
||||||
# GCC's false -Warray-bounds error with JSON_DIAGNOSTICS only shows up when optimizing (#5742).
|
|
||||||
# -O3 makes the optimizer-driven warnings of the ci_test_gcc flag set (-Winline,
|
|
||||||
# -Wsuggest-attribute=...) fire on the library's inline functions; they are not
|
|
||||||
# what this test checks, so turn them off for it.
|
|
||||||
json_test_set_test_options(test-diagnostics-optimized
|
|
||||||
COMPILE_OPTIONS $<$<CXX_COMPILER_ID:GNU>:-O3 -Werror=array-bounds -Wno-inline -Wno-suggest-attribute=pure -Wno-suggest-attribute=const>
|
|
||||||
)
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# add unit tests
|
# add unit tests
|
||||||
#############################################################################
|
#############################################################################
|
||||||
@@ -294,33 +288,6 @@ elseif(NOT json_32bit_test)
|
|||||||
list(FILTER files EXCLUDE REGEX src/unit-32bit.cpp)
|
list(FILTER files EXCLUDE REGEX src/unit-32bit.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# with JSON_TestShard=<index>/<count>, keep every <count>-th unit test file,
|
|
||||||
# starting at <index> (the glob is sorted, so the split is stable)
|
|
||||||
set(test_shard_index 0)
|
|
||||||
if(NOT "${JSON_TestShard}" STREQUAL "")
|
|
||||||
if(NOT JSON_TestShard MATCHES "^([0-9]+)/([1-9][0-9]*)$")
|
|
||||||
message(FATAL_ERROR "JSON_TestShard must be <index>/<count>, e.g. 0/2, not '${JSON_TestShard}'.")
|
|
||||||
endif()
|
|
||||||
set(test_shard_index ${CMAKE_MATCH_1})
|
|
||||||
set(test_shard_count ${CMAKE_MATCH_2})
|
|
||||||
if(NOT test_shard_index LESS test_shard_count)
|
|
||||||
message(FATAL_ERROR "JSON_TestShard: the index must be less than the count, not '${JSON_TestShard}'.")
|
|
||||||
endif()
|
|
||||||
list(LENGTH files test_file_count)
|
|
||||||
set(shard_files "")
|
|
||||||
set(file_position 0)
|
|
||||||
foreach(file ${files})
|
|
||||||
math(EXPR file_shard "${file_position} % ${test_shard_count}")
|
|
||||||
if(file_shard EQUAL test_shard_index)
|
|
||||||
list(APPEND shard_files ${file})
|
|
||||||
endif()
|
|
||||||
math(EXPR file_position "${file_position} + 1")
|
|
||||||
endforeach()
|
|
||||||
set(files ${shard_files})
|
|
||||||
list(LENGTH files shard_file_count)
|
|
||||||
message(STATUS "Test shard ${JSON_TestShard}: ${shard_file_count} of ${test_file_count} unit test files")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
foreach(file ${files})
|
foreach(file ${files})
|
||||||
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
||||||
endforeach()
|
endforeach()
|
||||||
@@ -338,9 +305,6 @@ if(json_32bit_test_only)
|
|||||||
return()
|
return()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# the following variants of single test files are only built in the first shard
|
|
||||||
if(test_shard_index EQUAL 0)
|
|
||||||
|
|
||||||
# test legacy comparison of discarded values
|
# test legacy comparison of discarded values
|
||||||
json_test_set_test_options(test-comparison_legacy
|
json_test_set_test_options(test-comparison_legacy
|
||||||
COMPILE_DEFINITIONS JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
|
COMPILE_DEFINITIONS JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
|
||||||
@@ -387,7 +351,6 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$" AND NOT MSVC)
|
|||||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
|
||||||
|
|
||||||
# *DO NOT* use json_test_set_test_options() below this line
|
# *DO NOT* use json_test_set_test_options() below this line
|
||||||
|
|
||||||
|
|||||||
@@ -48,10 +48,6 @@ TEST_CASE("default namespace")
|
|||||||
expected += "_sbu8";
|
expected += "_sbu8";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
expected += "_ekmo";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
|
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
|
||||||
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR);
|
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR);
|
||||||
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json";
|
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json";
|
||||||
|
|||||||
@@ -49,10 +49,6 @@ TEST_CASE("default namespace without version component")
|
|||||||
expected += "_sbu8";
|
expected += "_sbu8";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
|
||||||
expected += "_ekmo";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
expected += "::basic_json";
|
expected += "::basic_json";
|
||||||
|
|
||||||
// fallback for Clang
|
// fallback for Clang
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
build/
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# json_view compared with other libraries
|
||||||
|
|
||||||
|
The in-tree benchmarks in [`tests/benchmarks`](../README.md) measure `json_document` against `json::parse` only. The
|
||||||
|
programs here compare it with [yyjson](https://github.com/ibireme/yyjson),
|
||||||
|
[simdjson](https://github.com/simdjson/simdjson), and [Boost.JSON](https://github.com/boostorg/json): the question
|
||||||
|
users ask when they pick a library. They are not built by CMake or run by CI.
|
||||||
|
|
||||||
|
## Reproducing the numbers
|
||||||
|
|
||||||
|
`compare.py` builds the programs against `include/` of this checkout, runs them, and writes the results together with
|
||||||
|
everything needed to reproduce them to `results/<date>-<host>.md` (and `.csv`): the date, the commit, the CPU, the
|
||||||
|
OS, the compiler, the flags, and the versions of all libraries.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 tests/benchmarks/json_view/compare.py --data <json_test_data directory> [--native] [--rounds 30]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `--data` is the downloaded [test data](https://github.com/nlohmann/json_test_data), e.g. the `test_files` directory
|
||||||
|
of a CMake build directory. It needs `nativejson-benchmark/{twitter,citm_catalog,canada}.json` and
|
||||||
|
`jeopardy/jeopardy.json`.
|
||||||
|
- The other libraries come from the system: pkg-config, or Homebrew (`brew install yyjson simdjson boost`). With
|
||||||
|
`--download`, pinned releases are downloaded instead and checked against their SHA-256. Without Boost headers (or
|
||||||
|
with `--no-boost`), the Boost.JSON columns are skipped, and the results say so.
|
||||||
|
- `--corpus file...` adds files to the corpus benchmark, e.g. those of
|
||||||
|
[simdjson-data](https://github.com/simdjson/simdjson-data) or the
|
||||||
|
[yyjson benchmark](https://github.com/ibireme/yyjson_benchmark).
|
||||||
|
- Only the Python 3 standard library is used; a C++17 compiler is needed (`CXX` and `CC` are honored).
|
||||||
|
|
||||||
|
For numbers worth publishing, use a quiet machine (see [Getting stable numbers](../README.md#getting-stable-numbers)),
|
||||||
|
the default 30 rounds or more, and `--native` only if the other libraries were built for the same CPU.
|
||||||
|
|
||||||
|
### On GitHub-hosted runners
|
||||||
|
|
||||||
|
The workflow [json_view benchmarks](../../../.github/workflows/json_view_benchmarks.yml) runs `compare.py --download`
|
||||||
|
on demand: by hand (Actions → "json_view benchmarks" → "Run workflow"), on an x86-64 or AArch64 Ubuntu runner with GCC
|
||||||
|
or Clang, or when a pull request gets the label `benchmark`, on both architectures with GCC. The results appear as the
|
||||||
|
job summary and as an artifact. Shared runners are noisy, so these numbers show
|
||||||
|
where `json_view` stands on another architecture; they are not meant for publication.
|
||||||
|
|
||||||
|
## What is measured
|
||||||
|
|
||||||
|
`bench_view.cpp` runs four workloads on twitter, citm_catalog, canada, jeopardy, a single tweet (`status`), and a
|
||||||
|
JSON-RPC request (`rpc`):
|
||||||
|
|
||||||
|
| workload | what it does |
|
||||||
|
|---|---|
|
||||||
|
| parse | build and free a document |
|
||||||
|
| traverse | parse, then visit every value, convert every number, touch every string and key |
|
||||||
|
| select | parse, then read a few fields per record (e.g. id, user name, and retweet count of each tweet) |
|
||||||
|
| dump | serialize a parsed document (compact) |
|
||||||
|
|
||||||
|
`bench_corpus.cpp` runs parse, traverse, and dump on any list of files, so that no library is tuned to a handful of
|
||||||
|
documents. Its dump also writes the numbers as they are in the input: `json_view` with `number_format::source`, and
|
||||||
|
yyjson with numbers read as raw text (`YYJSON_READ_NUMBER_AS_RAW`), without converting them.
|
||||||
|
|
||||||
|
`bench_edit.cpp` measures read-modify-write: parse, apply the same logical edits with each library's own API, and
|
||||||
|
serialize (compact). Workloads: `patch` (a handful of edits at fixed places) and `update` (edits in every record).
|
||||||
|
An editable `json_document` edits in place; yyjson copies its immutable document into a mutable one first
|
||||||
|
(`yyjson_doc_mut_copy`); Boost.JSON and `json::parse` build mutable DOMs; simdjson cannot edit a document. All
|
||||||
|
outputs are checked to describe the same value.
|
||||||
|
|
||||||
|
Before anything is timed, all engines must accept each document and agree on the traversal: the number of values, the
|
||||||
|
bytes of all strings and keys, and the sum of all numbers. All engines run interleaved in every round, and the best
|
||||||
|
round is reported, as time and as a factor of the `json_view` time (below 1 means faster than `json_view`). Each timed
|
||||||
|
call follows an untimed call of the same engine: otherwise the engine after `json::parse` pays for the allocator
|
||||||
|
cleaning up the tens of thousands of nodes `json::parse` just freed (with glibc, this made `json_view` look 1.7 times
|
||||||
|
slower on citm_catalog traverse).
|
||||||
|
|
||||||
|
The engines do not all offer the same features, which the numbers should be read with:
|
||||||
|
|
||||||
|
| engine | document | random access | editable | notes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `json_view` | immutable index into the text | yes | no | a fresh document per parse; "reused" parses into the same document |
|
||||||
|
| yyjson | immutable (`yyjson_read`) | yes | via a mutable copy | |
|
||||||
|
| simdjson DOM | immutable, parser reused | yes | no | "fresh" uses a new parser per parse |
|
||||||
|
| simdjson On-Demand | none: forward-only, lazy | no | no | only traverse and select |
|
||||||
|
| Boost.JSON | owning, mutable DOM | yes | yes | monotonic resource |
|
||||||
|
| `json::parse` | owning, mutable DOM | yes | yes | |
|
||||||
|
|
||||||
|
Reusing memory matters as much as the parser. simdjson DOM reuses its parser, so it writes into memory it already
|
||||||
|
touched; a fresh `json_view` document or yyjson document gets new memory for every parse. On Linux, glibc returns large
|
||||||
|
blocks to the system when they are freed, so every fresh parse of a large document pays a page fault per 4 KiB page:
|
||||||
|
on x86-64 Linux, a fresh `json_view` parse of jeopardy took about twice as long as a reused one. On macOS on Apple
|
||||||
|
silicon, with 16 KiB pages, the difference is much smaller. Compare "json_view (reused)" with "simdjson DOM", and the
|
||||||
|
fresh `json_view` with "simdjson DOM (fresh)" and yyjson.
|
||||||
|
|
||||||
|
## Published results
|
||||||
|
|
||||||
|
Results are only published with the file `compare.py` wrote, which names the machine and the versions; see
|
||||||
|
`results/`. Numbers from one machine and compiler do not carry over to another: rerun the script.
|
||||||
@@ -0,0 +1,342 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// Corpus benchmark: the read-only workloads of bench_view.cpp on any list of
|
||||||
|
// JSON files (for example the benchmark sets of simdjson and yyjson).
|
||||||
|
//
|
||||||
|
// ./bench_corpus [--rounds N] file...
|
||||||
|
//
|
||||||
|
// For every file, all engines must accept it and agree on a traversal (value
|
||||||
|
// count, string bytes, sum of numbers) before anything is timed. Workloads:
|
||||||
|
// parse (build and free a document), traverse (visit every value, convert
|
||||||
|
// every number), dump (compact), and for json_view also dump with the source
|
||||||
|
// number text, compared with yyjson writing numbers read as raw text
|
||||||
|
// (YYJSON_READ_NUMBER_AS_RAW). Results go to bench_corpus.csv.
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
#include <boost/json.hpp>
|
||||||
|
#include <boost/json/src.hpp>
|
||||||
|
#endif
|
||||||
|
#include <simdjson.h>
|
||||||
|
#include <yyjson.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::json_document;
|
||||||
|
using nlohmann::json_view;
|
||||||
|
|
||||||
|
static volatile double g_sink;
|
||||||
|
|
||||||
|
struct stats
|
||||||
|
{
|
||||||
|
double num = 0;
|
||||||
|
std::size_t str = 0, nodes = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void walk(json_view v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.type())
|
||||||
|
{
|
||||||
|
case json::value_t::object:
|
||||||
|
for (auto it = v.begin(); it != v.end(); ++it)
|
||||||
|
{
|
||||||
|
st.str += it.key().size();
|
||||||
|
walk(*it, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::array:
|
||||||
|
for (const json_view e : v)
|
||||||
|
{
|
||||||
|
walk(e, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::string:
|
||||||
|
st.str += v.get_string().size();
|
||||||
|
break;
|
||||||
|
case json::value_t::number_integer:
|
||||||
|
st.num += static_cast<double>(v.get<std::int64_t>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_unsigned:
|
||||||
|
st.num += static_cast<double>(v.get<std::uint64_t>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_float:
|
||||||
|
st.num += v.get<double>();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(yyjson_val* v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (yyjson_get_type(v))
|
||||||
|
{
|
||||||
|
case YYJSON_TYPE_OBJ:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* k, * val;
|
||||||
|
yyjson_obj_foreach(v, idx, max, k, val)
|
||||||
|
{
|
||||||
|
st.str += yyjson_get_len(k);
|
||||||
|
walk(val, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_ARR:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* val;
|
||||||
|
yyjson_arr_foreach(v, idx, max, val)
|
||||||
|
{
|
||||||
|
walk(val, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_STR:
|
||||||
|
st.str += yyjson_get_len(v);
|
||||||
|
break;
|
||||||
|
case YYJSON_TYPE_NUM:
|
||||||
|
st.num += yyjson_is_sint(v) ? static_cast<double>(yyjson_get_sint(v)) : yyjson_is_uint(v) ? static_cast<double>(yyjson_get_uint(v)) : yyjson_get_real(v);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(simdjson::dom::element e, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (e.type())
|
||||||
|
{
|
||||||
|
case simdjson::dom::element_type::OBJECT:
|
||||||
|
for (auto f : simdjson::dom::object(e))
|
||||||
|
{
|
||||||
|
st.str += f.key.size();
|
||||||
|
walk(f.value, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::ARRAY:
|
||||||
|
for (auto c : simdjson::dom::array(e))
|
||||||
|
{
|
||||||
|
walk(c, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::STRING:
|
||||||
|
st.str += std::string_view(e).size();
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::INT64:
|
||||||
|
st.num += static_cast<double>(int64_t(e));
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::UINT64:
|
||||||
|
st.num += static_cast<double>(uint64_t(e));
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::DOUBLE:
|
||||||
|
st.num += double(e);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
static void walk(const boost::json::value& v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.kind())
|
||||||
|
{
|
||||||
|
case boost::json::kind::object:
|
||||||
|
for (const auto& kv : v.get_object())
|
||||||
|
{
|
||||||
|
st.str += kv.key().size();
|
||||||
|
walk(kv.value(), st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case boost::json::kind::array:
|
||||||
|
for (const auto& c : v.get_array())
|
||||||
|
{
|
||||||
|
walk(c, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case boost::json::kind::string:
|
||||||
|
st.str += v.get_string().size();
|
||||||
|
break;
|
||||||
|
case boost::json::kind::int64:
|
||||||
|
st.num += static_cast<double>(v.get_int64());
|
||||||
|
break;
|
||||||
|
case boost::json::kind::uint64:
|
||||||
|
st.num += static_cast<double>(v.get_uint64());
|
||||||
|
break;
|
||||||
|
case boost::json::kind::double_:
|
||||||
|
st.num += v.get_double();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static std::string slurp(const std::string& p)
|
||||||
|
{
|
||||||
|
std::ifstream f(p, std::ios::binary);
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "cannot open %s\n", p.c_str());
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << f.rdbuf();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool same(const stats& a, const stats& b)
|
||||||
|
{
|
||||||
|
return a.nodes == b.nodes && a.str == b.str && (a.num == b.num || std::fabs(a.num - b.num) <= 1e-9 * std::fabs(a.num));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
int rounds = 0; // 0: by size
|
||||||
|
std::vector<std::string> files;
|
||||||
|
for (int i = 1; i < argc; ++i)
|
||||||
|
{
|
||||||
|
if (std::strcmp(argv[i], "--rounds") == 0 && i + 1 < argc)
|
||||||
|
{
|
||||||
|
rounds = std::atoi(argv[++i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
files.push_back(argv[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::FILE* csv = std::fopen("bench_corpus.csv", "w");
|
||||||
|
std::fprintf(csv, "file,bytes,workload,engine,ns\n");
|
||||||
|
json_document reused;
|
||||||
|
simdjson::dom::parser sj;
|
||||||
|
for (const auto& path : files)
|
||||||
|
{
|
||||||
|
const std::string s = slurp(path);
|
||||||
|
const std::string name = path.substr(path.rfind('/') + 1);
|
||||||
|
const simdjson::padded_string ps(s);
|
||||||
|
|
||||||
|
// all engines must agree before timing
|
||||||
|
stats a, b, c, d;
|
||||||
|
const json_document doc = json_document::parse(s);
|
||||||
|
walk(doc.root(), a);
|
||||||
|
yyjson_doc* y = yyjson_read(s.data(), s.size(), 0);
|
||||||
|
auto sjr = sj.parse(ps);
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
boost::json::parse_options opt;
|
||||||
|
opt.numbers = boost::json::number_precision::precise;
|
||||||
|
boost::json::monotonic_resource mr0;
|
||||||
|
const boost::json::value bv = boost::json::parse(s, &mr0, opt);
|
||||||
|
#endif
|
||||||
|
if (y == nullptr || sjr.error())
|
||||||
|
{
|
||||||
|
std::printf("%-34s skipped (an engine rejects it)\n", name.c_str());
|
||||||
|
yyjson_doc_free(y);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
walk(yyjson_doc_get_root(y), b);
|
||||||
|
walk(sjr.value_unsafe(), c);
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
walk(bv, d);
|
||||||
|
#else
|
||||||
|
d = a;
|
||||||
|
#endif
|
||||||
|
yyjson_doc_free(y);
|
||||||
|
const bool ok = same(a, b) && same(a, c) && same(a, d);
|
||||||
|
|
||||||
|
const int r = rounds > 0 ? rounds : static_cast<int>(std::max<std::size_t>(3, std::min<std::size_t>(60, 400000000 / (s.size() + 1))));
|
||||||
|
struct engine
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::function<void()> fn;
|
||||||
|
};
|
||||||
|
json_document vd = json_document::parse(s);
|
||||||
|
yyjson_doc* yd = yyjson_read(s.data(), s.size(), 0);
|
||||||
|
yyjson_doc* yd_raw = yyjson_read(s.data(), s.size(), YYJSON_READ_NUMBER_AS_RAW);
|
||||||
|
simdjson::dom::parser sjd;
|
||||||
|
const simdjson::dom::element se = sjd.parse(ps).value_unsafe();
|
||||||
|
const std::vector<std::pair<std::string, std::vector<engine>>> workloads =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"parse", {
|
||||||
|
{"json_view", [&] { auto x = json_document::parse(s); g_sink = static_cast<double>(x.node_count()); }},
|
||||||
|
{"json_view (reused)", [&] { reused.read(s); g_sink = static_cast<double>(reused.node_count()); }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* x = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(x)); yyjson_doc_free(x); }},
|
||||||
|
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
|
{"simdjson DOM (fresh)", [&] { simdjson::dom::parser p; auto e = p.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); g_sink = v.is_object(); }},
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"traverse", {
|
||||||
|
{"json_view", [&] { auto x = json_document::parse(s); stats st; walk(x.root(), st); g_sink = st.num; }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* x = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(x), st); g_sink = st.num; yyjson_doc_free(x); }},
|
||||||
|
{"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); stats st; walk(v, st); g_sink = st.num; }},
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dump", {
|
||||||
|
{"json_view", [&] { std::string o = vd.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"yyjson", [&] { std::size_t n = 0; char* o = yyjson_write(yd, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
|
||||||
|
{"simdjson DOM", [&] { std::string o = simdjson::to_string(se); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"json_view (source numbers)", [&] { std::string o = vd.root().dump(-1, ' ', false, json_view::number_format::source); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"yyjson (raw numbers)", [&] { std::size_t n = 0; char* o = yyjson_write(yd_raw, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
std::printf("%-34s %9zu B%s\n", name.c_str(), s.size(), ok ? "" : " [ENGINES DISAGREE]");
|
||||||
|
for (const auto& wl : workloads)
|
||||||
|
{
|
||||||
|
std::vector<double> best(wl.second.size(), 1e300);
|
||||||
|
for (int i = 0; i < r; ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
{
|
||||||
|
// an untimed call first: whatever the previous engine left to the allocator
|
||||||
|
// (e.g. thousands of freed json nodes) is cleaned up here, not in the timing
|
||||||
|
wl.second[k].fn();
|
||||||
|
const auto t0 = std::chrono::steady_clock::now();
|
||||||
|
wl.second[k].fn();
|
||||||
|
best[k] = std::min(best[k], std::chrono::duration<double, std::nano>(std::chrono::steady_clock::now() - t0).count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::printf(" %-9s", wl.first.c_str());
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
{
|
||||||
|
std::printf(" %s %.2f GB/s (%.2fx)", wl.second[k].name.c_str(), static_cast<double>(s.size()) / best[k], best[k] / best[0]);
|
||||||
|
std::fprintf(csv, "%s,%zu,%s,%s,%.1f\n", name.c_str(), s.size(), wl.first.c_str(), wl.second[k].name.c_str(), best[k]);
|
||||||
|
}
|
||||||
|
std::printf("\n");
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
yyjson_doc_free(yd);
|
||||||
|
yyjson_doc_free(yd_raw);
|
||||||
|
}
|
||||||
|
std::fclose(csv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,583 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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
|
||||||
|
|
||||||
|
// Read-modify-write benchmark: parse a document, apply the same logical edits
|
||||||
|
// with each library's own API, and serialize it (compact).
|
||||||
|
//
|
||||||
|
// json_view json_editable_document: edits in place, unchanged values stay in the index
|
||||||
|
// yyjson yyjson_read + yyjson_doc_mut_copy (the way to edit a parsed document)
|
||||||
|
// Boost.JSON parse into a mutable DOM (monotonic resource, precise numbers), serialize
|
||||||
|
// json::parse nlohmann::json today
|
||||||
|
// simdjson has no mutable document and is not part of this comparison.
|
||||||
|
//
|
||||||
|
// Workloads:
|
||||||
|
// patch a handful of edits at fixed places (scalars, a new member, a new array element)
|
||||||
|
// update edits in every record (twitter: 100 statuses, citm: 243 performances,
|
||||||
|
// canada: 480 rings, jeopardy: 216,930 questions): set scalars, erase a
|
||||||
|
// member, add a member (canada: replace the first point of every ring)
|
||||||
|
//
|
||||||
|
// Build: see README.md (same flags as bench_view.cpp).
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
#include <boost/json.hpp>
|
||||||
|
#include <boost/json/src.hpp>
|
||||||
|
#endif
|
||||||
|
#include <yyjson.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::json_editable_document;
|
||||||
|
using nlohmann::json_editable_view;
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
namespace bj = boost::json;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static volatile std::size_t g_sink;
|
||||||
|
|
||||||
|
// ---------------- json_view ----------------
|
||||||
|
|
||||||
|
static std::string edit_view(const std::string& name, const std::string& s, bool update)
|
||||||
|
{
|
||||||
|
json_editable_document d = json_editable_document::parse(s);
|
||||||
|
const json_editable_view r = d.root();
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::int64_t i = 0;
|
||||||
|
for (const json_editable_view st : r["statuses"])
|
||||||
|
{
|
||||||
|
d.set(st, "retweet_count", i++);
|
||||||
|
d.set(st, "favorited", true);
|
||||||
|
d.set(st, "text", "redacted");
|
||||||
|
d.erase(st, "entities");
|
||||||
|
d.set(st, "edited", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d.set(r["search_metadata"], "count", 200);
|
||||||
|
d.set(r["statuses"][0], "text", "patched");
|
||||||
|
d.set(r["statuses"][0]["user"], "followers_count", 1);
|
||||||
|
d.set(r["statuses"][99], "favorited", true);
|
||||||
|
d.set(r, "patched", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (const json_editable_view p : r["performances"])
|
||||||
|
{
|
||||||
|
d.set(p, "name", "performance");
|
||||||
|
d.set(p, "start", p["start"].get<std::int64_t>() + 1);
|
||||||
|
d.erase(p, "seatMapImage");
|
||||||
|
d.set(p, "edited", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d.set(r["events"]["138586341"], "name", "patched");
|
||||||
|
d.set(r["performances"][0], "start", 0);
|
||||||
|
d.set(r["venueNames"], "PLEYEL_PLEYEL", "Salle");
|
||||||
|
d.set(r, "patched", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
const json_editable_view coords = r["features"][0]["geometry"]["coordinates"];
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (const json_editable_view ring : coords)
|
||||||
|
{
|
||||||
|
d.set(ring, 0, json::array({0.5, 0.5}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d.set(r["features"][0]["properties"], "name", "patched");
|
||||||
|
d.set(r, "type", "FeatureCollection2");
|
||||||
|
d.set(coords[0], 0, json::array({0.0, 0.0}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (const json_editable_view q : r)
|
||||||
|
{
|
||||||
|
d.set(q, "value", "$1");
|
||||||
|
d.erase(q, "air_date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d.set(r[0], "value", "$0");
|
||||||
|
d.set(r[100000], "answer", "patched");
|
||||||
|
d.set(r[216929], "round", "x");
|
||||||
|
d.push_back(r, json::object({{"category", "NEW"}, {"value", "$5"}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "status")
|
||||||
|
{
|
||||||
|
d.set(r, "retweet_count", 1);
|
||||||
|
d.set(r["user"], "name", "x");
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
d.set(r, "id", 4);
|
||||||
|
d.set(r["params"], "subtrahend", 24);
|
||||||
|
}
|
||||||
|
return r.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- nlohmann::json ----------------
|
||||||
|
|
||||||
|
static std::string edit_json(const std::string& name, const std::string& s, bool update)
|
||||||
|
{
|
||||||
|
json r = json::parse(s);
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::int64_t i = 0;
|
||||||
|
for (auto& st : r["statuses"])
|
||||||
|
{
|
||||||
|
st["retweet_count"] = i++;
|
||||||
|
st["favorited"] = true;
|
||||||
|
st["text"] = "redacted";
|
||||||
|
st.erase("entities");
|
||||||
|
st["edited"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r["search_metadata"]["count"] = 200;
|
||||||
|
r["statuses"][0]["text"] = "patched";
|
||||||
|
r["statuses"][0]["user"]["followers_count"] = 1;
|
||||||
|
r["statuses"][99]["favorited"] = true;
|
||||||
|
r["patched"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& p : r["performances"])
|
||||||
|
{
|
||||||
|
p["name"] = "performance";
|
||||||
|
p["start"] = p["start"].get<std::int64_t>() + 1;
|
||||||
|
p.erase("seatMapImage");
|
||||||
|
p["edited"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r["events"]["138586341"]["name"] = "patched";
|
||||||
|
r["performances"][0]["start"] = 0;
|
||||||
|
r["venueNames"]["PLEYEL_PLEYEL"] = "Salle";
|
||||||
|
r["patched"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
json& coords = r["features"][0]["geometry"]["coordinates"];
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& ring : coords)
|
||||||
|
{
|
||||||
|
ring[0] = json::array({0.5, 0.5});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r["features"][0]["properties"]["name"] = "patched";
|
||||||
|
r["type"] = "FeatureCollection2";
|
||||||
|
coords[0][0] = json::array({0.0, 0.0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& q : r)
|
||||||
|
{
|
||||||
|
q["value"] = "$1";
|
||||||
|
q.erase("air_date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r[0]["value"] = "$0";
|
||||||
|
r[100000]["answer"] = "patched";
|
||||||
|
r[216929]["round"] = "x";
|
||||||
|
r.push_back(json::object({{"category", "NEW"}, {"value", "$5"}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "status")
|
||||||
|
{
|
||||||
|
r["retweet_count"] = 1;
|
||||||
|
r["user"]["name"] = "x";
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
r["id"] = 4;
|
||||||
|
r["params"]["subtrahend"] = 24;
|
||||||
|
}
|
||||||
|
return r.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- yyjson ----------------
|
||||||
|
|
||||||
|
static std::string edit_yyjson(const std::string& name, const std::string& s, bool update)
|
||||||
|
{
|
||||||
|
yyjson_doc* idoc = yyjson_read(s.data(), s.size(), 0);
|
||||||
|
yyjson_mut_doc* d = yyjson_doc_mut_copy(idoc, nullptr);
|
||||||
|
yyjson_doc_free(idoc);
|
||||||
|
yyjson_mut_val* r = yyjson_mut_doc_get_root(d);
|
||||||
|
auto get = [](yyjson_mut_val * o, const char* k)
|
||||||
|
{
|
||||||
|
return yyjson_mut_obj_get(o, k);
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
yyjson_mut_val* sts = get(r, "statuses");
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_mut_val* st;
|
||||||
|
std::int64_t i = 0;
|
||||||
|
yyjson_mut_arr_foreach(sts, idx, max, st)
|
||||||
|
{
|
||||||
|
yyjson_mut_set_sint(get(st, "retweet_count"), i++);
|
||||||
|
yyjson_mut_set_bool(get(st, "favorited"), true);
|
||||||
|
yyjson_mut_set_str(get(st, "text"), "redacted");
|
||||||
|
yyjson_mut_obj_remove_key(st, "entities");
|
||||||
|
yyjson_mut_obj_add_bool(d, st, "edited", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yyjson_mut_set_sint(get(get(r, "search_metadata"), "count"), 200);
|
||||||
|
yyjson_mut_val* s0 = yyjson_mut_arr_get(sts, 0);
|
||||||
|
yyjson_mut_set_str(get(s0, "text"), "patched");
|
||||||
|
yyjson_mut_set_sint(get(get(s0, "user"), "followers_count"), 1);
|
||||||
|
yyjson_mut_set_bool(get(yyjson_mut_arr_get(sts, 99), "favorited"), true);
|
||||||
|
yyjson_mut_obj_add_bool(d, r, "patched", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_mut_val* p;
|
||||||
|
yyjson_mut_arr_foreach(get(r, "performances"), idx, max, p)
|
||||||
|
{
|
||||||
|
yyjson_mut_set_str(get(p, "name"), "performance");
|
||||||
|
yyjson_mut_val* start = get(p, "start");
|
||||||
|
yyjson_mut_set_sint(start, yyjson_mut_get_sint(start) + 1);
|
||||||
|
yyjson_mut_obj_remove_key(p, "seatMapImage");
|
||||||
|
yyjson_mut_obj_add_bool(d, p, "edited", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yyjson_mut_set_str(get(get(get(r, "events"), "138586341"), "name"), "patched");
|
||||||
|
yyjson_mut_set_sint(get(yyjson_mut_arr_get(get(r, "performances"), 0), "start"), 0);
|
||||||
|
yyjson_mut_set_str(get(get(r, "venueNames"), "PLEYEL_PLEYEL"), "Salle");
|
||||||
|
yyjson_mut_obj_add_bool(d, r, "patched", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
yyjson_mut_val* f0 = yyjson_mut_arr_get(get(r, "features"), 0);
|
||||||
|
yyjson_mut_val* coords = get(get(f0, "geometry"), "coordinates");
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
static const double half[2] = {0.5, 0.5};
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_mut_val* ring;
|
||||||
|
yyjson_mut_arr_foreach(coords, idx, max, ring)
|
||||||
|
{
|
||||||
|
yyjson_mut_arr_replace(ring, 0, yyjson_mut_arr_with_real(d, half, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
static const double zero[2] = {0.0, 0.0};
|
||||||
|
yyjson_mut_set_str(get(get(f0, "properties"), "name"), "patched");
|
||||||
|
yyjson_mut_set_str(get(r, "type"), "FeatureCollection2");
|
||||||
|
yyjson_mut_arr_replace(yyjson_mut_arr_get(coords, 0), 0, yyjson_mut_arr_with_real(d, zero, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_mut_val* q;
|
||||||
|
yyjson_mut_arr_foreach(r, idx, max, q)
|
||||||
|
{
|
||||||
|
yyjson_mut_set_str(get(q, "value"), "$1");
|
||||||
|
yyjson_mut_obj_remove_key(q, "air_date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yyjson_mut_set_str(get(yyjson_mut_arr_get(r, 0), "value"), "$0");
|
||||||
|
yyjson_mut_set_str(get(yyjson_mut_arr_get(r, 100000), "answer"), "patched");
|
||||||
|
yyjson_mut_set_str(get(yyjson_mut_arr_get(r, 216929), "round"), "x");
|
||||||
|
yyjson_mut_val* o = yyjson_mut_obj(d);
|
||||||
|
yyjson_mut_obj_add_str(d, o, "category", "NEW");
|
||||||
|
yyjson_mut_obj_add_str(d, o, "value", "$5");
|
||||||
|
yyjson_mut_arr_append(r, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "status")
|
||||||
|
{
|
||||||
|
yyjson_mut_set_sint(get(r, "retweet_count"), 1);
|
||||||
|
yyjson_mut_set_str(get(get(r, "user"), "name"), "x");
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
yyjson_mut_set_sint(get(r, "id"), 4);
|
||||||
|
yyjson_mut_set_sint(get(get(r, "params"), "subtrahend"), 24);
|
||||||
|
}
|
||||||
|
std::size_t n = 0;
|
||||||
|
char* out = yyjson_mut_write(d, 0, &n);
|
||||||
|
std::string result(out, n);
|
||||||
|
std::free(out);
|
||||||
|
yyjson_mut_doc_free(d);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
// ---------------- Boost.JSON ----------------
|
||||||
|
|
||||||
|
static std::string edit_boost(const std::string& name, const std::string& s, bool update)
|
||||||
|
{
|
||||||
|
bj::monotonic_resource mr;
|
||||||
|
bj::parse_options opt;
|
||||||
|
opt.numbers = bj::number_precision::precise; // correctly rounded, like the others
|
||||||
|
bj::value v = bj::parse(s, &mr, opt);
|
||||||
|
bj::object* const obj = v.if_object(); // nullptr for jeopardy (an array)
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
bj::array& sts = (*obj)["statuses"].as_array();
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::int64_t i = 0;
|
||||||
|
for (auto& e : sts)
|
||||||
|
{
|
||||||
|
bj::object& st = e.as_object();
|
||||||
|
st["retweet_count"] = i++;
|
||||||
|
st["favorited"] = true;
|
||||||
|
st["text"] = "redacted";
|
||||||
|
st.erase("entities");
|
||||||
|
st["edited"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(*obj)["search_metadata"].as_object()["count"] = 200;
|
||||||
|
bj::object& s0 = sts[0].as_object();
|
||||||
|
s0["text"] = "patched";
|
||||||
|
s0["user"].as_object()["followers_count"] = 1;
|
||||||
|
sts[99].as_object()["favorited"] = true;
|
||||||
|
(*obj)["patched"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& e : (*obj)["performances"].as_array())
|
||||||
|
{
|
||||||
|
bj::object& p = e.as_object();
|
||||||
|
p["name"] = "performance";
|
||||||
|
p["start"] = p["start"].as_int64() + 1;
|
||||||
|
p.erase("seatMapImage");
|
||||||
|
p["edited"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(*obj)["events"].as_object()["138586341"].as_object()["name"] = "patched";
|
||||||
|
(*obj)["performances"].as_array()[0].as_object()["start"] = 0;
|
||||||
|
(*obj)["venueNames"].as_object()["PLEYEL_PLEYEL"] = "Salle";
|
||||||
|
(*obj)["patched"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
bj::object& f0 = (*obj)["features"].as_array()[0].as_object();
|
||||||
|
bj::array& coords = f0["geometry"].as_object()["coordinates"].as_array();
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& ring : coords)
|
||||||
|
{
|
||||||
|
ring.as_array()[0] = bj::array({0.5, 0.5});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
f0["properties"].as_object()["name"] = "patched";
|
||||||
|
(*obj)["type"] = "FeatureCollection2";
|
||||||
|
coords[0].as_array()[0] = bj::array({0.0, 0.0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
bj::array& a = v.as_array();
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& e : a)
|
||||||
|
{
|
||||||
|
bj::object& q = e.as_object();
|
||||||
|
q["value"] = "$1";
|
||||||
|
q.erase("air_date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
a[0].as_object()["value"] = "$0";
|
||||||
|
a[100000].as_object()["answer"] = "patched";
|
||||||
|
a[216929].as_object()["round"] = "x";
|
||||||
|
a.push_back(bj::object({{"category", "NEW"}, {"value", "$5"}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "status")
|
||||||
|
{
|
||||||
|
(*obj)["retweet_count"] = 1;
|
||||||
|
(*obj)["user"].as_object()["name"] = "x";
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
(*obj)["id"] = 4;
|
||||||
|
(*obj)["params"].as_object()["subtrahend"] = 24;
|
||||||
|
}
|
||||||
|
return bj::serialize(v);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---------------- harness ----------------
|
||||||
|
|
||||||
|
static std::string slurp(const std::string& p)
|
||||||
|
{
|
||||||
|
std::ifstream f(p, std::ios::binary);
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "cannot open %s\n", p.c_str());
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << f.rdbuf();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
if (argc < 2)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "usage: %s <json_test_data directory> [rounds] [document]\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const std::string T = std::string(argv[1]) + "/";
|
||||||
|
const int rounds = argc > 2 ? std::atoi(argv[2]) : 20;
|
||||||
|
const std::string only = argc > 3 ? argv[3] : "";
|
||||||
|
struct doc
|
||||||
|
{
|
||||||
|
std::string name, text;
|
||||||
|
int batch;
|
||||||
|
};
|
||||||
|
std::vector<doc> docs;
|
||||||
|
for (const char* f :
|
||||||
|
{"nativejson-benchmark/twitter.json", "nativejson-benchmark/citm_catalog.json", "nativejson-benchmark/canada.json", "jeopardy/jeopardy.json"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
std::string n = std::string(f).substr(std::string(f).find('/') + 1);
|
||||||
|
docs.push_back({n.substr(0, n.size() - 5), slurp(T + f), 1});
|
||||||
|
}
|
||||||
|
docs.push_back({"status", json::parse(docs[0].text)["statuses"][0].dump(), 200});
|
||||||
|
docs.push_back({"rpc", R"({"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 3})", 5000});
|
||||||
|
|
||||||
|
using fn = std::string (*)(const std::string&, const std::string&, bool);
|
||||||
|
const std::vector<std::pair<std::string, fn>> engines =
|
||||||
|
{
|
||||||
|
{"json_view", edit_view}, {"yyjson", edit_yyjson},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", edit_boost},
|
||||||
|
#endif
|
||||||
|
{"json::parse", edit_json}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::FILE* csv = std::fopen("bench_edit.csv", "w");
|
||||||
|
std::fprintf(csv, "doc,bytes,workload,engine,ns\n");
|
||||||
|
for (const auto& dc : docs)
|
||||||
|
{
|
||||||
|
if (!only.empty() && dc.name != only)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const bool update :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if (update && (dc.name == "status" || dc.name == "rpc"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// all engines must produce the same value
|
||||||
|
const json expected = json::parse(edit_json(dc.name, dc.text, update));
|
||||||
|
bool ok = true;
|
||||||
|
for (const auto& e : engines)
|
||||||
|
{
|
||||||
|
ok = ok && json::parse(e.second(dc.name, dc.text, update)) == expected;
|
||||||
|
}
|
||||||
|
std::vector<double> best(engines.size(), 1e300);
|
||||||
|
const int r = dc.text.size() > 10000000 ? std::max(3, rounds / 4) : rounds;
|
||||||
|
for (int i = 0; i < r; ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t k = 0; k < engines.size(); ++k)
|
||||||
|
{
|
||||||
|
// an untimed call first: whatever the previous engine left to the allocator
|
||||||
|
// (e.g. thousands of freed json nodes) is cleaned up here, not in the timing
|
||||||
|
g_sink = engines[k].second(dc.name, dc.text, update).size();
|
||||||
|
const auto t0 = std::chrono::steady_clock::now();
|
||||||
|
for (int b = 0; b < dc.batch; ++b)
|
||||||
|
{
|
||||||
|
g_sink = engines[k].second(dc.name, dc.text, update).size();
|
||||||
|
}
|
||||||
|
const double ns = std::chrono::duration<double, std::nano>(std::chrono::steady_clock::now() - t0).count() / dc.batch;
|
||||||
|
best[k] = std::min(best[k], ns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const char* wl = update ? "update" : "patch";
|
||||||
|
std::printf("%-13s %-7s %s", dc.name.c_str(), wl, ok ? "" : "[OUTPUT MISMATCH] ");
|
||||||
|
for (std::size_t k = 0; k < engines.size(); ++k)
|
||||||
|
{
|
||||||
|
const double us = best[k] / 1e3;
|
||||||
|
std::printf(" %s %.*fus (%.2fx)", engines[k].first.c_str(), us < 10 ? 3 : (us < 1000 ? 1 : 0), us, best[k] / best[0]);
|
||||||
|
std::fprintf(csv, "%s,%zu,%s,%s,%.1f\n", dc.name.c_str(), dc.text.size(), wl, engines[k].first.c_str(), best[k]);
|
||||||
|
}
|
||||||
|
std::printf("\n");
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::fclose(csv);
|
||||||
|
}
|
||||||
Loaded 100 of 153 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user