mirror of
https://github.com/nlohmann/json.git
synced 2026-10-07 06:57: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
@@ -16,9 +16,6 @@ only_commits:
|
||||
|
||||
environment:
|
||||
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
|
||||
configuration: Debug
|
||||
platform: x86
|
||||
@@ -37,13 +34,7 @@ environment:
|
||||
configuration: Release
|
||||
platform: x86
|
||||
CXX_FLAGS: "/permissive- /std:c++17 /utf-8 /W4 /WX"
|
||||
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=0/2"
|
||||
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"
|
||||
CMAKE_OPTIONS: ""
|
||||
GENERATOR: Visual Studio 15 2017
|
||||
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||
@@ -64,13 +55,7 @@ environment:
|
||||
configuration: Release
|
||||
platform: x64
|
||||
CXX_FLAGS: "/permissive- /std:c++17 /Zc:__cplusplus /utf-8 /W4 /WX"
|
||||
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=0/2"
|
||||
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"
|
||||
CMAKE_OPTIONS: ""
|
||||
GENERATOR: Visual Studio 15 2017
|
||||
|
||||
init:
|
||||
@@ -81,7 +66,7 @@ install:
|
||||
- if "%platform%"=="x86" set GENERATOR_PLATFORM=Win32
|
||||
|
||||
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:
|
||||
- cmake --build . --config "%configuration%" --parallel 2
|
||||
|
||||
+4
-3
@@ -51,12 +51,13 @@ labels:
|
||||
- "include/nlohmann/detail/view/.*"
|
||||
- "single_include/nlohmann/json_view\\.hpp"
|
||||
- "tests/src/unit-json_view.*"
|
||||
- "tests/src/fuzzer-parse_json_view\\.cpp"
|
||||
- "tests/src/fuzzer-(parse_json_view|json_view_image)\\.cpp"
|
||||
- "tests/benchmarks/json_view/.*"
|
||||
- "tools/amalgamate/config_json_view\\.json"
|
||||
- "docs/mkdocs/docs/features/json_view\\.md"
|
||||
- "docs/mkdocs/docs/api/basic_json_(document|view)/.*"
|
||||
- "docs/mkdocs/docs/api/(ordered_)?json_(document|view)\\.md"
|
||||
- "docs/mkdocs/docs/examples/(basic_json_(document|view)__|(ordered_)?json_(document|view)).*"
|
||||
- "docs/mkdocs/docs/api/(ordered_)?json_(editable_)?(document|view)\\.md"
|
||||
- "docs/mkdocs/docs/examples/(basic_json_(document|view)__|(ordered_)?json_(editable_)?(document|view)).*"
|
||||
- "tests/benchmarks/src/benchmarks_view\\.cpp"
|
||||
|
||||
- label: "aspect: json_view"
|
||||
|
||||
@@ -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
|
||||
|
||||
jobs:
|
||||
macos-15:
|
||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||
macos-14:
|
||||
runs-on: macos-14 # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
|
||||
strategy:
|
||||
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:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
|
||||
@@ -36,11 +36,11 @@ jobs:
|
||||
- name: Test
|
||||
run: cd build ; ctest -j 10 --output-on-failure
|
||||
|
||||
macos-26:
|
||||
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
|
||||
macos-15:
|
||||
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['26.4.1', '26.5', '26.6']
|
||||
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1']
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
container: ubuntu:24.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_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:
|
||||
- name: Install build-essential
|
||||
run: apt-get update ; apt-get install -y build-essential unzip wget git
|
||||
@@ -209,7 +209,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
# 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 }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
@@ -70,7 +70,10 @@ cc_library(
|
||||
"include/nlohmann/detail/view/builder.hpp",
|
||||
"include/nlohmann/detail/view/compare.hpp",
|
||||
"include/nlohmann/detail/view/document_data.hpp",
|
||||
"include/nlohmann/detail/view/edit.hpp",
|
||||
"include/nlohmann/detail/view/edit_storage.hpp",
|
||||
"include/nlohmann/detail/view/errors.hpp",
|
||||
"include/nlohmann/detail/view/image.hpp",
|
||||
"include/nlohmann/detail/view/input.hpp",
|
||||
"include/nlohmann/detail/view/iterator.hpp",
|
||||
"include/nlohmann/detail/view/lookup.hpp",
|
||||
|
||||
@@ -62,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_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_DeleteDeprecatedFunctions "Delete the deprecated functions instead of only deprecating them." OFF)
|
||||
|
||||
if (JSON_CI)
|
||||
include(ci)
|
||||
@@ -124,10 +123,6 @@ if (JSON_StrictBinaryUTF8)
|
||||
message(STATUS "Strict UTF-8 checks in binary writers enabled (JSON_STRICT_BINARY_UTF8=1)")
|
||||
endif()
|
||||
|
||||
if (JSON_DeleteDeprecatedFunctions)
|
||||
message(STATUS "Deprecated functions are deleted (JSON_DELETE_DEPRECATED_FUNCTIONS=1)")
|
||||
endif()
|
||||
|
||||
if (JSON_Diagnostic_Positions)
|
||||
message(STATUS "Diagnostic positions enabled (JSON_DIAGNOSTIC_POSITIONS=1)")
|
||||
endif()
|
||||
@@ -164,7 +159,6 @@ target_compile_definitions(
|
||||
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
|
||||
$<$<BOOL:${JSON_StrictNulHandling}>:JSON_STRICT_NUL_HANDLING=1>
|
||||
$<$<BOOL:${JSON_StrictBinaryUTF8}>:JSON_STRICT_BINARY_UTF8=1>
|
||||
$<$<BOOL:${JSON_DeleteDeprecatedFunctions}>:JSON_DELETE_DEPRECATED_FUNCTIONS=1>
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
|
||||
+2
-16
@@ -249,20 +249,6 @@ add_custom_target(ci_test_strict_nul_handling
|
||||
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.
|
||||
###############################################################################
|
||||
@@ -376,7 +362,7 @@ add_custom_target(ci_test_coverage
|
||||
# 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
|
||||
COMMAND CXX=${CLANG_TOOL} CXXFLAGS=${CLANG_CXX_FLAGS_SANITIZER} ${CMAKE_COMMAND}
|
||||
@@ -722,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
|
||||
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_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_4_0_0 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0})
|
||||
|
||||
|
||||
@@ -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
|
||||
+10
-2
@@ -135,14 +135,20 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json::~basic_json', 'Me
|
||||
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::accept', 'Function', 'api/basic_json_document/accept/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::erase', 'Method', 'api/basic_json_document/erase/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::insert', 'Method', 'api/basic_json_document/insert/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::is_discarded', 'Method', 'api/basic_json_document/is_discarded/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::load', 'Function', 'api/basic_json_document/load/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::memory_usage', 'Method', 'api/basic_json_document/memory_usage/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::node_count', 'Method', 'api/basic_json_document/node_count/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::owns_source', 'Method', 'api/basic_json_document/owns_source/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::parse', 'Function', 'api/basic_json_document/parse/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::parse_copy', 'Function', 'api/basic_json_document/parse_copy/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::push_back', 'Method', 'api/basic_json_document/push_back/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::read', 'Method', 'api/basic_json_document/read/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::root', 'Method', 'api/basic_json_document/root/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::save', 'Method', 'api/basic_json_document/save/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::set', 'Method', 'api/basic_json_document/set/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::shrink_to_fit', 'Method', 'api/basic_json_document/shrink_to_fit/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::source', 'Method', 'api/basic_json_document/source/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view', 'Class', 'api/basic_json_view/index.html');
|
||||
@@ -191,6 +197,8 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::type_name',
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_view::value', 'Method', 'api/basic_json_view/value/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json', 'Class', 'api/json/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json_document', 'Class', 'api/json_document/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json_editable_document', 'Class', 'api/json_editable_document/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json_editable_view', 'Class', 'api/json_editable_view/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json_view', 'Class', 'api/json_view/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer', 'Class', 'api/json_pointer/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('json_pointer::back', 'Method', 'api/json_pointer/back/index.html');
|
||||
@@ -230,6 +238,8 @@ INSERT INTO searchIndex(name, type, path) VALUES ('operator<<', 'Operator', 'api
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('operator>>', 'Operator', 'api/operator_gtgt/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('ordered_json', 'Class', 'api/ordered_json/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('ordered_json_document', 'Class', 'api/ordered_json_document/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('ordered_json_editable_document', 'Class', 'api/ordered_json_editable_document/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('ordered_json_editable_view', 'Class', 'api/ordered_json_editable_view/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('ordered_json_view', 'Class', 'api/ordered_json_view/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('ordered_map', 'Class', 'api/ordered_map/index.html');
|
||||
INSERT INTO searchIndex(name, type, path) VALUES ('std::formatter<basic_json>', 'Class', 'api/basic_json/std_formatter/index.html');
|
||||
@@ -281,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_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_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_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');
|
||||
@@ -312,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_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_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 ('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');
|
||||
|
||||
@@ -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
|
||||
[`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
|
||||
source.patch(diff(source, target)) == target;
|
||||
```
|
||||
@@ -27,7 +27,7 @@ a JSON patch to convert the `source` to `target`
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
@@ -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 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.
|
||||
|
||||
!!! 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
|
||||
|
||||
- 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.
|
||||
@@ -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
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
formats), too large or small integer numbers will automatically be stored as [`number_unsigned_t`](number_unsigned_t.md)
|
||||
or [`number_float_t`](number_float_t.md).
|
||||
range will yield over/underflow when used in a constructor. During deserialization, too large or small integer numbers
|
||||
will automatically be stored as [`number_unsigned_t`](number_unsigned_t.md) or [`number_float_t`](number_float_t.md).
|
||||
|
||||
[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
|
||||
|
||||
@@ -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
|
||||
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
|
||||
integer numbers will automatically be stored as [`number_integer_t`](number_integer_t.md) or
|
||||
[`number_float_t`](number_float_t.md).
|
||||
when used in a constructor. During deserialization, too large or small integer numbers will automatically be stored
|
||||
as [`number_integer_t`](number_integer_t.md) or [`number_float_t`](number_float_t.md).
|
||||
|
||||
[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
|
||||
|
||||
@@ -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
|
||||
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)
|
||||
- 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
|
||||
|
||||
@@ -121,6 +119,4 @@ Linear in the size of the JSON value `j`.
|
||||
- BJData version parameter (for draft3 binary encoding) added in version 3.12.0.
|
||||
- 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
|
||||
[`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.
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
@@ -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
|
||||
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)
|
||||
- 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
|
||||
|
||||
@@ -113,8 +110,6 @@ pass before anything is written.
|
||||
- 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.
|
||||
- `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
|
||||
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
|
||||
|
||||
@@ -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
|
||||
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)
|
||||
- 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
|
||||
|
||||
@@ -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
|
||||
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`.
|
||||
- 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)"`
|
||||
- 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`
|
||||
- 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
|
||||
|
||||
@@ -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;
|
||||
before, integers could be serialized with the wrong value if `number_integer_t` was narrower than
|
||||
`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
|
||||
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)
|
||||
- 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
|
||||
|
||||
@@ -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
|
||||
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`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid UBJSON.
|
||||
@@ -0,0 +1,128 @@
|
||||
# <small>nlohmann::basic_json_document::</small>erase
|
||||
|
||||
```cpp
|
||||
// (1)
|
||||
std::size_t erase(view_type object, string_view_t key);
|
||||
|
||||
// (2)
|
||||
template<typename I>
|
||||
void erase(view_type array, I idx);
|
||||
|
||||
// (3)
|
||||
std::size_t erase(const json_pointer& ptr);
|
||||
```
|
||||
|
||||
Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md))
|
||||
has `erase`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`).
|
||||
|
||||
1. Removes every member of `object` whose key is `key` (see [Notes](#notes) on duplicate keys) and returns how many
|
||||
were removed; `#!cpp 0` if `object` has no member with this key.
|
||||
2. Removes the element at index `idx` of `array`, which must already exist (`#!cpp idx < array.size()`).
|
||||
3. Removes the value the JSON pointer `ptr` refers to, relative to [`root()`](root.md), and returns how many values
|
||||
were removed: the *parent* of the target must already exist, and the target itself is removed as in 1. (an object
|
||||
member; `#!cpp 0` or more) or 2. (an array element; always `#!cpp 1`). `ptr` must not be empty -- [`root()`](root.md)
|
||||
itself cannot be erased.
|
||||
|
||||
## Template parameters
|
||||
|
||||
`I`
|
||||
: an integral type other than `#!cpp bool`, deduced (overloads taking a `#!cpp bool` or a non-integral type for
|
||||
`idx` do not participate in overload resolution).
|
||||
|
||||
## Parameters
|
||||
|
||||
`object` (in)
|
||||
: the object to remove a member of
|
||||
|
||||
`array` (in)
|
||||
: the array to remove an element of
|
||||
|
||||
`key` (in)
|
||||
: the key of the member(s) to remove
|
||||
|
||||
`idx` (in)
|
||||
: the index of the element to remove; a negative value throws (see [Exceptions](#exceptions))
|
||||
|
||||
`ptr` (in)
|
||||
: a JSON pointer to the value to remove, relative to `root()`
|
||||
|
||||
## Return value
|
||||
|
||||
1. the number of removed members (`#!cpp 0` if `object` had none with this `key`)
|
||||
2. (nothing)
|
||||
3. the number of removed values (`#!cpp 0` or more for an object member, always `#!cpp 1` for an array element)
|
||||
|
||||
## Exceptions
|
||||
|
||||
1. Throws [`type_error.307`](../../home/exceptions.md#jsonexceptiontype_error307) if `object` is not an object -- the
|
||||
same message [`BasicJsonType::erase`](../basic_json/erase.md) throws for the same type.
|
||||
2. Throws `type_error.307` if `array` is not an array. Throws
|
||||
[`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if `idx` is negative, or if
|
||||
`#!cpp idx >= array.size()`.
|
||||
3. Throws [`out_of_range.405`](../../home/exceptions.md#jsonexceptionout_of_range405) ("JSON pointer has no parent")
|
||||
if `ptr` is empty. Throws what [`at`](../basic_json_view/at.md) throws (overload 3) for resolving `ptr`'s parent.
|
||||
For the last reference token itself: if the parent is an array, throws what 2. throws for an index that is out of
|
||||
range, or, for a token that is not a valid array index,
|
||||
[`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) (a leading `#!cpp '0'`),
|
||||
[`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) (not a number),
|
||||
[`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) (too large for `size_type`), or
|
||||
[`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) (an empty token); otherwise (an
|
||||
object, or a primitive value the pointer's parent resolves to) throws what 1. throws.
|
||||
|
||||
Every overload also throws [`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view
|
||||
does not belong to this document") if `object`/`array` is a [discarded](../basic_json_view/is_discarded.md) view or a
|
||||
view of a *different* document (overloads 1-2 only; overload 3 always starts from this document's own
|
||||
[`root()`](root.md)).
|
||||
|
||||
## Complexity
|
||||
|
||||
1. Linear in the number of members of `object`.
|
||||
2. Linear in the number of elements of `array` at or after `idx` (they move one slot over).
|
||||
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||
that level or the index into the array (as [`at`](../basic_json_view/at.md)), plus the complexity of 1. or 2. for
|
||||
the last token.
|
||||
|
||||
## Notes
|
||||
|
||||
!!! info "Duplicate keys"
|
||||
|
||||
Overload 1. removes *every* member with `key`, not just the first -- unlike [`set`](set.md), which assigns the
|
||||
first occurrence and drops the rest. This is why it returns a count rather than a single view: there may be
|
||||
more than one member removed, or none.
|
||||
|
||||
Like [`set`](set.md) and [`push_back`](push_back.md), `erase` never moves an element's *value*: a view still
|
||||
referring to a removed member or element keeps showing what it last held (see [Edits](index.md#edits)) -- it just no
|
||||
longer appears when `array`/`object` is read, dumped, or iterated. Removing an element of `array` (2.) does shift the
|
||||
*links* to the elements after it, the same way `insert`, `set`, or `push_back` on the same array would; any iterator
|
||||
already taken over `array`/`object` is invalidated by an erase, since it was walking the old layout.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below drops a deprecated field and a decommissioned entry from a configuration document -- using all
|
||||
three overloads -- and shows what stays intact that would not with a plain `json`/`ordered_json` value: the order
|
||||
of the fields around the ones removed, and the exact spelling of a number that was never touched.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_document__erase.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_document__erase.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [insert](insert.md) - insert an element into an array
|
||||
- [set](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers to
|
||||
- [push_back](push_back.md) - append to an array
|
||||
- [root](root.md) - the view of the root value, the starting point of overload 3
|
||||
- [`BasicJsonType::erase`](../basic_json/erase.md) - the corresponding function of `basic_json`
|
||||
- [Edits](index.md#edits) - what an edit guarantees, for every overload
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -3,7 +3,7 @@
|
||||
<small>Defined in header `<nlohmann/json_view.hpp>`</small>
|
||||
|
||||
```cpp
|
||||
template<typename BasicJsonType>
|
||||
template<typename BasicJsonType, bool Editable = false>
|
||||
class basic_json_document;
|
||||
```
|
||||
|
||||
@@ -19,6 +19,11 @@ it (a copy, or an rvalue `#!cpp std::string` that was moved in); see [`owns_sour
|
||||
is move-only: copying a document would either duplicate a potentially large index and text, or leave two documents
|
||||
claiming to borrow the same buffer, so it is disabled.
|
||||
|
||||
With `#!cpp Editable == true`, the document also offers [`set`](set.md), [`push_back`](push_back.md),
|
||||
[`insert`](insert.md), and [`erase`](erase.md) to change values in place, see [Edits](#edits) below. The source text
|
||||
itself is never written; a read-only document (`#!cpp Editable == false`, the default) does not carry any of the
|
||||
bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp static_assert`).
|
||||
|
||||
## Template parameters
|
||||
|
||||
`BasicJsonType`
|
||||
@@ -26,23 +31,37 @@ claiming to borrow the same buffer, so it is disabled.
|
||||
[`ordered_json`](../ordered_json.md). Only 64-bit `number_integer_t`/`number_unsigned_t` types are supported; this
|
||||
is checked with a `static_assert`.
|
||||
|
||||
`Editable`
|
||||
: whether the document supports [`set`](set.md), [`push_back`](push_back.md), [`insert`](insert.md), and
|
||||
[`erase`](erase.md) (optional, `#!cpp false` by default). See [Edits](#edits) below.
|
||||
|
||||
## Specializations
|
||||
|
||||
- [**json_document**](../json_document.md) - documents of the default specialization [`json`](../json.md)
|
||||
- [**ordered_json_document**](../ordered_json_document.md) - documents of [`ordered_json`](../ordered_json.md)
|
||||
- [**json_document**](../json_document.md) - read-only documents of the default specialization [`json`](../json.md)
|
||||
- [**ordered_json_document**](../ordered_json_document.md) - read-only documents of
|
||||
[`ordered_json`](../ordered_json.md)
|
||||
- [**json_editable_document**](../json_editable_document.md) - editable documents of [`json`](../json.md)
|
||||
- [**ordered_json_editable_document**](../ordered_json_editable_document.md) - editable documents of
|
||||
[`ordered_json`](../ordered_json.md)
|
||||
|
||||
## Member types
|
||||
|
||||
- **view_type** - the type of view returned by [`root()`](root.md) (`#!cpp basic_json_view<BasicJsonType>`)
|
||||
- **view_type** - the type of view returned by [`root()`](root.md) (`#!cpp basic_json_view<BasicJsonType, Editable>`)
|
||||
- **value_t** - the JSON type enumeration, see [`basic_json::value_t`](../basic_json/value_t.md)
|
||||
|
||||
## Member functions
|
||||
|
||||
- [(constructor)](basic_json_document.md)
|
||||
|
||||
### Parsing
|
||||
|
||||
- [**parse**](parse.md) (_static_) - deserialize from a compatible input, borrowing or owning it as appropriate
|
||||
- [**parse_copy**](parse_copy.md) (_static_) - deserialize a copy of a compatible input
|
||||
- [**accept**](accept.md) (_static_) - check whether the input is valid JSON
|
||||
- [**read**](read.md) - (re-)parse into this document, reusing its memory
|
||||
|
||||
### Access
|
||||
|
||||
- [**root**](root.md) - the view of the root value
|
||||
- [**is_discarded**](is_discarded.md) - return whether the last parse failed
|
||||
- [**source**](source.md) - the parsed text
|
||||
@@ -51,6 +70,49 @@ claiming to borrow the same buffer, so it is disabled.
|
||||
- [**memory_usage**](memory_usage.md) - the number of bytes held by the document
|
||||
- [**shrink_to_fit**](shrink_to_fit.md) - release unused index capacity
|
||||
|
||||
### Images
|
||||
|
||||
- [**save**](save.md) - the document as an image that `load()` reads without parsing
|
||||
- [**load**](load.md) (_static_) - read an image written by `save()`
|
||||
|
||||
### Edits
|
||||
|
||||
- [**set**](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers
|
||||
to (`#!cpp Editable` documents only)
|
||||
- [**push_back**](push_back.md) - append to an array (`#!cpp Editable` documents only)
|
||||
- [**insert**](insert.md) - insert an element into an array before a given position (`#!cpp Editable` documents only)
|
||||
- [**erase**](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to
|
||||
(`#!cpp Editable` documents only)
|
||||
|
||||
## Edits
|
||||
|
||||
An editable document (`#!cpp Editable == true`) can be changed after parsing, with [`set`](set.md),
|
||||
[`push_back`](push_back.md), [`insert`](insert.md), and [`erase`](erase.md);
|
||||
[`json_editable_document`](../json_editable_document.md) and
|
||||
[`ordered_json_editable_document`](../ordered_json_editable_document.md) are the corresponding specializations. A few
|
||||
points apply to every edit:
|
||||
|
||||
- **The source text is never written**, and the parsed index never moves: every value keeps the node it was parsed
|
||||
into, so [views](../basic_json_view/index.md) taken before an edit stay valid, including
|
||||
[`root()`](root.md). New values (and the element sequences of an edited array/object) go to storage owned by the
|
||||
document, allocated on demand.
|
||||
- **A view keeps referring to the same value.** After [`set`](set.md) replaces the value a view refers to, that view
|
||||
sees the new value; a view of a value that a later edit replaces or drops keeps showing what it last held. An edit
|
||||
of an array or object, however, **invalidates the iterators taken over it** (its members may now live in a
|
||||
different sequence), and a string obtained with [`get_string()`](../basic_json_view/get_string.md) stays valid even
|
||||
as further edits happen (earlier buffers of edited text are kept alive, not overwritten).
|
||||
- **Values are accepted three ways:** a [`basic_json_view`](../basic_json_view/index.md) of *any* document
|
||||
(read-only or editable; it is copied, nothing is shared with the source document), a `BasicJsonType` value, or
|
||||
anything `BasicJsonType` can be constructed from (numbers, strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...).
|
||||
- [`dump()`](../basic_json_view/dump.md) writes an edited document with members in document order, new members at
|
||||
the end, and, with [`number_format::source`](../basic_json_view/number_format.md), keeps the spelling of every
|
||||
number that was not itself edited -- see [Editing a document](../../features/json_view.md#editing-a-document) for
|
||||
why this matters.
|
||||
- [`read()`](read.md) discards all edits, [`shrink_to_fit()`](shrink_to_fit.md) does not move the node index once
|
||||
there are edits, and [`memory_usage()`](memory_usage.md) includes the memory edits use.
|
||||
[`source_offset()`](../basic_json_view/source_offset.md) of a value introduced by an edit is
|
||||
`#!cpp static_cast<std::size_t>(-1)`, the same value it reports for a decoded string.
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -0,0 +1,114 @@
|
||||
# <small>nlohmann::basic_json_document::</small>insert
|
||||
|
||||
```cpp
|
||||
template<typename I, typename V>
|
||||
view_type insert(view_type array, I idx, V&& value);
|
||||
```
|
||||
|
||||
Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md))
|
||||
has `insert`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`).
|
||||
|
||||
Inserts `value` into `array` as a new element before position `idx`, which must not be past the end
|
||||
(`#!cpp idx <= array.size()`; `#!cpp idx == array.size()` appends, like [`push_back`](push_back.md)). Unlike
|
||||
[`push_back`](push_back.md), a [null](../basic_json_view/is_null.md) `array` does *not* first become an empty array:
|
||||
`array` must already be an array.
|
||||
|
||||
`value` is accepted three ways: a [`basic_json_view`](../basic_json_view/index.md) of *any* document -- read-only or
|
||||
editable, and it does not have to be `array`'s own document -- which is copied so that nothing is shared with the
|
||||
source document afterward; a `BasicJsonType` value; or anything `BasicJsonType` can be constructed from (numbers,
|
||||
strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...).
|
||||
|
||||
## Template parameters
|
||||
|
||||
`I`
|
||||
: an integral type other than `#!cpp bool`, deduced (overloads taking a `#!cpp bool` or a non-integral type for
|
||||
`idx` do not participate in overload resolution).
|
||||
|
||||
`V`
|
||||
: the type of `value`, deduced; see above for what is accepted.
|
||||
|
||||
## Parameters
|
||||
|
||||
`array` (in)
|
||||
: the array to insert into
|
||||
|
||||
`idx` (in)
|
||||
: the position to insert `value` before; a negative value throws (see [Exceptions](#exceptions))
|
||||
|
||||
`value` (in)
|
||||
: the value to insert
|
||||
|
||||
## Return value
|
||||
|
||||
a view of the new element, now holding `value`
|
||||
|
||||
## Exception safety
|
||||
|
||||
Basic exception safety: `value` is fully encoded -- including the checks below -- into storage owned by the document
|
||||
before anything already reachable from [`root()`](root.md) is touched, so a failure while encoding `value` (an
|
||||
invalid argument, or `#!cpp std::bad_alloc`) leaves the document completely unchanged, other than memory allocated
|
||||
for the encoding that is not reclaimed. A failure of a later allocation -- while `array` switches from its parsed
|
||||
layout to a growable block, or while that block grows, see [Notes](#notes) -- can still leave `array` already
|
||||
switched to that layout even though `value` itself was not inserted.
|
||||
|
||||
## Exceptions
|
||||
|
||||
Throws [`type_error.309`](../../home/exceptions.md#jsonexceptiontype_error309) if `array` is not an array -- the same
|
||||
message [`BasicJsonType::insert`](../basic_json/insert.md) throws for the same type; a null `array` throws this too
|
||||
(see above). Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if `idx` is negative,
|
||||
or if `#!cpp idx > array.size()`. Throws
|
||||
[`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view does not belong to this
|
||||
document") if `array` is a [discarded](../basic_json_view/is_discarded.md) view or a view of a *different* document.
|
||||
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if `value` is a
|
||||
[discarded](../basic_json_view/is_discarded.md) view or a [discarded](../basic_json/is_discarded.md) `BasicJsonType`
|
||||
value, and [`type_error.319`](../../home/exceptions.md#jsonexceptiontype_error319) if `value` is (or contains) a
|
||||
binary value -- `BasicJsonType` can hold one, but a `json_document` cannot. Throws
|
||||
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) if `value` is (or contains) a string that is
|
||||
not valid UTF-8, with the same message [`BasicJsonType::dump()`](../basic_json/dump.md) gives for that string.
|
||||
|
||||
## Complexity
|
||||
|
||||
Linear in the number of elements of `array` at or after `idx` (they move one slot over), plus time linear in the
|
||||
size of `value` to encode it into the document's storage (constant for a scalar, linear in the number of nested
|
||||
values for an array or object): like [`push_back`](push_back.md), the elements of `array` move to a growable block
|
||||
of links the first time it is inserted into (or [`set`](set.md)/[`push_back`](push_back.md) on), and that block
|
||||
grows in amortized constant time; inserting before the end within that block still shifts every later element.
|
||||
|
||||
## Notes
|
||||
|
||||
Like [`set`](set.md) on a member or an element, `insert` never moves an existing *element's value* -- only where
|
||||
`array`'s *links* to its elements live -- so a view of an existing element of `array` stays valid across an
|
||||
`insert`, and keeps referring to the same element even though its index shifts. Any iterator already taken over
|
||||
`array` is invalidated, since it was walking the old layout. See [Edits](index.md#edits) for what stays valid across
|
||||
an edit in general.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below inserts a step into the middle of a deployment plan, without touching the steps that come
|
||||
after it, and shows that a view taken before the insert keeps referring to the same element even though its
|
||||
index shifts -- something a plain `json`/`ordered_json` array, or its `std::vector`-based storage, has no
|
||||
equivalent for.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_document__insert.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_document__insert.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [push_back](push_back.md) - append to an array
|
||||
- [erase](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to
|
||||
- [set](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers to
|
||||
- [`BasicJsonType::insert`](../basic_json/insert.md) - the corresponding function of `basic_json`
|
||||
- [Edits](index.md#edits) - what an edit guarantees, for every overload
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -0,0 +1,169 @@
|
||||
# <small>nlohmann::basic_json_document::</small>load
|
||||
|
||||
```cpp
|
||||
// (1)
|
||||
static basic_json_document load(const std::uint8_t* image, std::size_t size,
|
||||
const image_check check = image_check::full);
|
||||
|
||||
// (2)
|
||||
static basic_json_document load(const std::vector<std::uint8_t>& image,
|
||||
const image_check check = image_check::full);
|
||||
|
||||
// (3)
|
||||
static basic_json_document load(std::vector<std::uint8_t>&& image,
|
||||
const image_check check = image_check::full);
|
||||
```
|
||||
|
||||
1. Reads an image [`save()`](save.md) wrote, from a pointer and a byte count. The image is **borrowed**: `image`
|
||||
must stay alive and unchanged for as long as the returned document, and any view taken from it, is used.
|
||||
2. Reads an image from a `#!cpp std::vector`. Also **borrowed** -- equivalent to overload 1 called with
|
||||
`#!cpp image.data()` and `#!cpp image.size()`.
|
||||
3. Reads an image, keeping the vector instead of copying it: `image` is moved into the document (no copy), which
|
||||
then owns it for as long as it needs the text and the decoded strings. [`owns_source()`](owns_source.md) is
|
||||
`#!cpp true` afterward.
|
||||
|
||||
In every overload, the node index is copied into storage the document itself owns -- so that it is properly aligned,
|
||||
and, for an [editable](index.md#edits) document, can be edited -- while the text and the decoded strings stay in
|
||||
`image`. The hash indexes [large objects](../../features/json_view.md) use for lookup are rebuilt, exactly as after
|
||||
parsing.
|
||||
|
||||
## Parameters
|
||||
|
||||
`image` (in)
|
||||
: the image [`save()`](save.md) wrote (overloads 1 and 2), or one to take ownership of (overload 3)
|
||||
|
||||
`size` (in)
|
||||
: the number of bytes at `image` (overload 1)
|
||||
|
||||
`check` (in)
|
||||
: how thoroughly to validate `image` before trusting it; see [`image_check`](#image_check) below (optional,
|
||||
`#!cpp image_check::full` by default)
|
||||
|
||||
## Return value
|
||||
|
||||
The document read from the image.
|
||||
|
||||
## Exception safety
|
||||
|
||||
Overloads 1 and 2 give the strong guarantee: `image` is only read, never written, so a thrown exception leaves the
|
||||
caller's buffer untouched.
|
||||
|
||||
Overload 3 moves `image` into the document *before* validating it, so that a good image is kept without a copy. If
|
||||
loading then fails, the partially built document -- and the vector now inside it -- is discarded along with the
|
||||
exception, and `image` itself is left **empty**, not restored to what was passed in. Move a copy in instead, or
|
||||
validate with overload 2 first, if the original vector must survive a failed load.
|
||||
|
||||
## Exceptions
|
||||
|
||||
On a big-endian target, throws [`type_error.320`](../../home/exceptions.md#jsonexceptiontype_error320) -- the same
|
||||
exception [`save()`](save.md#exceptions) throws there, since the image format is little-endian only.
|
||||
|
||||
Otherwise throws [`parse_error.116`](../../home/exceptions.md#jsonexceptionparse_error116) if `image` is not one
|
||||
`save()` could have written, or fails the requested `check`:
|
||||
|
||||
| message | when |
|
||||
|------------------------|------------------------------------------------------------------------------------------------------|
|
||||
| `too short` | `image` is `#!cpp nullptr`, or `size` is smaller than the 64-byte header |
|
||||
| `unknown format` | the header's magic bytes or version do not match, or a reserved header field is not zero |
|
||||
| `sizes out of range` | the node count, text size, or decoded-string size the header describes does not fit `size`, or the `#!cpp '\0'` after the text or after the decoded strings is missing |
|
||||
| `the check failed` | `check` is not `#!cpp image_check::none`, and the image fails it -- see [`image_check`](#image_check) |
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
```
|
||||
[json.exception.parse_error.116] parse error: invalid json_document image: too short
|
||||
```
|
||||
```
|
||||
[json.exception.parse_error.116] parse error: invalid json_document image: unknown format
|
||||
```
|
||||
```
|
||||
[json.exception.parse_error.116] parse error: invalid json_document image: sizes out of range
|
||||
```
|
||||
```
|
||||
[json.exception.parse_error.116] parse error: invalid json_document image: the check failed
|
||||
```
|
||||
|
||||
## Complexity
|
||||
|
||||
Linear in the number of nodes, which are always copied into the document. With `#!cpp check == image_check::full`,
|
||||
additionally linear in the combined length of the text and the decoded strings; `#!cpp image_check::bounds` and
|
||||
`#!cpp image_check::none` do not read them.
|
||||
|
||||
## Notes
|
||||
|
||||
**The `image_check` modes.**
|
||||
|
||||
```cpp
|
||||
using image_check = detail::view::image_check;
|
||||
|
||||
enum class image_check
|
||||
{
|
||||
full,
|
||||
bounds,
|
||||
none
|
||||
};
|
||||
```
|
||||
|
||||
How thoroughly `load()` validates `image` before trusting it.
|
||||
|
||||
| value | checks | guarantees |
|
||||
|----------|--------------------------------------------------------------------------------------------------------------|------------|
|
||||
| `full` | everything the parser itself guarantees: structure and bounds; that every string is valid UTF-8 (and, for a string still in the source text, that it contains no quote, backslash, or control character); and that every number token is well-formed and matches the value stored for it | reading and serializing a checked image is safe and always produces valid JSON, exactly as for a parsed document |
|
||||
| `bounds` | structure and bounds only -- that every offset and count in the node index stays inside the image | reading and serializing stay memory-safe, but a crafted image can hold strings that are not valid UTF-8 or that serialize to invalid JSON ([`dump()`](../basic_json_view/dump.md) writes them unchanged or throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316)), and numbers whose values differ from their text |
|
||||
| `none` | nothing | images from a trusted source only -- reading a damaged image is undefined behavior |
|
||||
|
||||
`full` is the default and the right choice for an image from anything you do not fully control -- a file, a cache
|
||||
shared with other processes, a peer on the network. `bounds` skips scanning the text and the decoded strings, so it
|
||||
fits a cache your own process just wrote and reads straight back, where damage would mean a bug or a hardware fault
|
||||
rather than adversarial input; it still cannot crash or read out of bounds. `none` skips validation entirely and
|
||||
should only be used for an image you trust as much as your own memory.
|
||||
|
||||
**Lifetime.** Overloads 1 and 2 borrow `image`: it must stay alive and byte-for-byte unchanged for as long as the
|
||||
returned document, and any [view](../basic_json_view/index.md) taken from it, is used -- exactly like a document
|
||||
[`parse()`](parse.md) borrowed its input for. Overload 3 avoids this by keeping the vector itself; see
|
||||
[`owns_source`](owns_source.md).
|
||||
|
||||
!!! warning "Experimental"
|
||||
|
||||
The image format is versioned but not yet stable, and may change in an incompatible way before it is declared
|
||||
stable; `load()` already rejects an image written by a different format version with `parse_error.116`
|
||||
("unknown format"). Use images to cache a document within one build of the library, or to hand one to another
|
||||
process running the *same* build on the *same* (little-endian) machine -- not as a long-term storage format.
|
||||
|
||||
**What `image_check::bounds` does not guarantee.** A bounds-checked image can never make `load()`,
|
||||
[`root()`](root.md), element access, or [`materialize()`](../basic_json_view/materialize.md) read outside the image,
|
||||
so those stay safe on a damaged one. It does *not* guarantee that the image describes valid JSON: a string
|
||||
that a `full` check would have rejected can make [`dump()`](../basic_json_view/dump.md) write invalid UTF-8 or invalid
|
||||
JSON, or throw `type_error.316`, and a number can read back with a value that does not match how it is spelled.
|
||||
Reserve `bounds` for images you already trust to be well-formed, and use it only to skip the extra scan.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Caching a document, ownership, and a rejected image"
|
||||
|
||||
The example below saves a parsed document as an image, checks that `load()` reproduces the original
|
||||
[`dump()`](../basic_json_view/dump.md) without parsing, and shows the difference between
|
||||
`load(std::move(image))` (owned) and `load(image)` (borrowed). It then damages one byte of the image and shows
|
||||
`image_check::full` rejecting it with `parse_error.116`, while `image_check::bounds` -- meant for a cache the
|
||||
process already trusts -- still reads it without going out of bounds.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_document__load.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_document__load.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [save](save.md) - write the document as an image
|
||||
- [owns_source](owns_source.md) - return whether the document holds its own copy of the text
|
||||
- [parse](parse.md) - deserialize from JSON text instead of an image
|
||||
- [Images](../../features/json_view.md#images) - why and when to use images
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -132,6 +132,7 @@ integer type becomes a floating-point value.
|
||||
- [accept](accept.md) - check whether the input is valid JSON
|
||||
- [read](read.md) - (re-)parse into this document, reusing its memory
|
||||
- [owns_source](owns_source.md) - return whether the document holds its own copy of the text
|
||||
- [load](load.md) - read a document from an image instead of parsing JSON text
|
||||
- [`BasicJsonType::parse`](../basic_json/parse.md) - the corresponding function of `basic_json`
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# <small>nlohmann::basic_json_document::</small>push_back
|
||||
|
||||
```cpp
|
||||
template<typename V>
|
||||
view_type push_back(view_type array, V&& value);
|
||||
```
|
||||
|
||||
Appends `value` as a new last element of `array`. A [null](../basic_json_view/is_null.md) `array` first becomes an
|
||||
empty array, the same way [`set`](set.md) turns a null `object` into an empty object.
|
||||
|
||||
`value` is accepted three ways: a [`basic_json_view`](../basic_json_view/index.md) of *any* document -- read-only or
|
||||
editable, and it does not have to be `array`'s own document -- which is copied so that nothing is shared with the
|
||||
source document afterward; a `BasicJsonType` value; or anything `BasicJsonType` can be constructed from (numbers,
|
||||
strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...).
|
||||
|
||||
Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md))
|
||||
has `push_back`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`).
|
||||
|
||||
## Template parameters
|
||||
|
||||
`V`
|
||||
: the type of `value`, deduced; see above for what is accepted.
|
||||
|
||||
## Parameters
|
||||
|
||||
`array` (in)
|
||||
: the array (or null value) to append to
|
||||
|
||||
`value` (in)
|
||||
: the value to append
|
||||
|
||||
## Return value
|
||||
|
||||
a view of the new last element of `array`, now holding `value`
|
||||
|
||||
## Exception safety
|
||||
|
||||
Basic exception safety: `value` is fully encoded -- including the checks below -- into storage owned by the document
|
||||
before anything already reachable from [`root()`](root.md) is touched, so a failure while encoding `value` (an
|
||||
invalid argument, or `#!cpp std::bad_alloc`) leaves the document completely unchanged, other than memory allocated
|
||||
for the encoding that is not reclaimed. A failure of a later allocation -- while `array` switches from its parsed
|
||||
layout to a growable block, or while that block grows, see [Notes](#notes) -- can still leave a partial effect, such
|
||||
as a null `array` argument already turned into an empty array even though `value` itself was not appended.
|
||||
|
||||
## Exceptions
|
||||
|
||||
Throws [`type_error.308`](../../home/exceptions.md#jsonexceptiontype_error308) if `array` is neither an array nor
|
||||
null -- the same message [`BasicJsonType::push_back`](../basic_json/push_back.md) throws for the same type. Throws
|
||||
[`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view does not belong to this
|
||||
document") if `array` is a [discarded](../basic_json_view/is_discarded.md) view or a view of a *different* document.
|
||||
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if `value` is a
|
||||
[discarded](../basic_json_view/is_discarded.md) view or a [discarded](../basic_json/is_discarded.md) `BasicJsonType`
|
||||
value, and [`type_error.319`](../../home/exceptions.md#jsonexceptiontype_error319) if `value` is (or contains) a
|
||||
binary value -- `BasicJsonType` can hold one, but a `json_document` cannot. Throws
|
||||
[`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) if `value` is (or contains) a string that is
|
||||
not valid UTF-8, with the same message [`BasicJsonType::dump()`](../basic_json/dump.md) gives for that string.
|
||||
|
||||
## Complexity
|
||||
|
||||
Amortized constant, plus time linear in the size of `value` to encode it into the document's storage (constant for
|
||||
a scalar, linear in the number of nested values for an array or object): the elements of `array` move to a growable
|
||||
block of links the first time it is appended to (or [`set`](set.md) on), and that block itself grows -- doubling its
|
||||
capacity, so the cost of growing it amortizes to constant per element -- only once it runs out of room. See
|
||||
[Notes](#notes).
|
||||
|
||||
## Notes
|
||||
|
||||
Like [`set`](set.md) on a member or an element, `push_back` never moves an existing element itself -- only where
|
||||
`array`'s *links* to its elements live -- so a view of an existing element of `array` stays valid across a
|
||||
`push_back`, but any iterator already taken over `array` is invalidated, since it was walking the old layout. See
|
||||
[Edits](index.md#edits) for what stays valid across an edit in general.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below appends records to an array one at a time, as they might arrive from a stream of events,
|
||||
without ever building a `BasicJsonType` value for the array or for the records already in it, and shows that a
|
||||
view taken from an earlier `push_back` still refers to the same element once later ones have run.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_document__push_back.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_document__push_back.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [set](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers to
|
||||
- [insert](insert.md) - insert an element into an array before a given position
|
||||
- [erase](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to
|
||||
- [root](root.md) - the view of the root value
|
||||
- [`BasicJsonType::push_back`](../basic_json/push_back.md) - the corresponding function of `basic_json`
|
||||
- [Edits](index.md#edits) - what an edit guarantees, for every overload
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -75,6 +75,7 @@ many documents of similar size should therefore keep one document and call `read
|
||||
|
||||
- [parse](parse.md) - deserialize from a compatible input
|
||||
- [root](root.md) - the view of the root value
|
||||
- [load](load.md) - read a document from an image instead of parsing JSON text
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
# <small>nlohmann::basic_json_document::</small>save
|
||||
|
||||
```cpp
|
||||
std::vector<std::uint8_t> save() const;
|
||||
```
|
||||
|
||||
Writes the document as an *image*: a byte buffer that [`load`](load.md) reads back without parsing. The image holds
|
||||
the node index, the source text (plus, for an edited document, the number tokens edits wrote), and the decoded
|
||||
strings (plus the strings edits wrote) -- everything [`root()`](root.md) needs, with nothing left to parse.
|
||||
|
||||
An edited document is written in its *current* state, with its values in document order, the way the library's own
|
||||
parser would have produced them for that JSON text: a member [`set`](set.md) added goes at the end, an
|
||||
[`erase`](erase.md)d member leaves no trace, and a float that is not finite (NaN or positive/negative infinity)
|
||||
becomes null, the same substitution [`dump()`](../basic_json_view/dump.md) makes. The same document always saves to
|
||||
the same bytes -- also across `BasicJsonType` and `#!cpp Editable`, since the image reflects document order and
|
||||
values only, not which specialization produced them.
|
||||
|
||||
## Return value
|
||||
|
||||
The image, as a `#!cpp std::vector<std::uint8_t>`. Pass it, or a pointer to its data together with its size, to
|
||||
[`load`](load.md) to read the document back.
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong guarantee: `save()` does not modify `#!cpp *this` (it is `#!cpp const`), so if it throws, the document is left
|
||||
exactly as it was, and the partially built image is discarded with the exception.
|
||||
|
||||
## Exceptions
|
||||
|
||||
Throws [`type_error.320`](../../home/exceptions.md#jsonexceptiontype_error320) if the document is
|
||||
[discarded](is_discarded.md) -- a default-constructed document, or one a failed [`parse()`](parse.md)/
|
||||
[`read()`](read.md) with `allow_exceptions == false` left discarded.
|
||||
|
||||
On a big-endian target, throws `type_error.320` with a different message instead: the image format is little-endian
|
||||
only (see [Notes](#notes)).
|
||||
|
||||
Throws [`out_of_range.416`](../../home/exceptions.md#jsonexceptionout_of_range416) if the node count, the text, or
|
||||
the decoded strings of the image would individually reach 4 GiB -- the same 32-bit offsets
|
||||
[`parse()`](parse.md#exceptions) and, for edits, [`set`](set.md)/[`push_back`](push_back.md) are already limited to.
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
```
|
||||
[json.exception.type_error.320] cannot save a discarded json_document
|
||||
```
|
||||
```
|
||||
[json.exception.type_error.320] json_document images need a little-endian target
|
||||
```
|
||||
```
|
||||
[json.exception.out_of_range.416] images of 4 GiB or more are not supported by json_document
|
||||
```
|
||||
|
||||
## Complexity
|
||||
|
||||
Linear in the size of the document: the number of nodes, plus the length of the text and the decoded strings that end
|
||||
up in the image.
|
||||
|
||||
## Notes
|
||||
|
||||
**Format.** The image begins with a 64-byte header (the magic bytes `#!cpp "NJVI"`, a version number, the node count,
|
||||
and the sizes of the text and the decoded strings, all little-endian), followed by the nodes
|
||||
([16 bytes each](../../home/architecture.md#node-index-of-json-views)), the text and a `#!cpp '\0'`, and the decoded
|
||||
strings and a `#!cpp '\0'`. [`load`](load.md) checks the header, and the sizes it describes, before reading anything
|
||||
else -- see [`load`'s Exceptions](load.md#exceptions).
|
||||
|
||||
!!! warning "Experimental"
|
||||
|
||||
The image format is versioned but not yet stable: it may change in an incompatible way before it is declared
|
||||
stable. Use images to cache a document within one build of the library, or to hand one to another process running
|
||||
the *same* build on the *same* (little-endian) machine -- not as a long-term storage format. Keep the original
|
||||
JSON text if you need to read a saved document back with a future library version.
|
||||
|
||||
**Little-endian only.** The image is written as raw little-endian bytes, with no byte-swapping. `save()` (and
|
||||
[`load`](load.md)) throw `type_error.320` on a big-endian target rather than silently produce bytes a big-endian
|
||||
reader could not interpret correctly.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Caching a parsed document as an image"
|
||||
|
||||
The example below saves a parsed configuration as an image -- the way a service might cache one to answer later
|
||||
requests without parsing the text again -- and confirms that loading it back gives exactly the same result as
|
||||
parsing did, and that saving is deterministic.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_document__save.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_document__save.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [load](load.md) - read an image written by `save()`
|
||||
- [owns_source](owns_source.md) - return whether the document holds its own copy of the text
|
||||
- [`basic_json_view::dump`](../basic_json_view/dump.md) - serialize the document to JSON text instead of an image
|
||||
- [Images](../../features/json_view.md#images) - why and when to use images
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -0,0 +1,194 @@
|
||||
# <small>nlohmann::basic_json_document::</small>set
|
||||
|
||||
```cpp
|
||||
// (1)
|
||||
template<typename V>
|
||||
view_type set(view_type target, V&& value);
|
||||
|
||||
// (2)
|
||||
template<typename V>
|
||||
view_type set(view_type object, string_view_t key, V&& value);
|
||||
|
||||
// (3)
|
||||
template<typename I, typename V>
|
||||
view_type set(view_type array, I idx, V&& value);
|
||||
|
||||
// (4)
|
||||
template<typename V>
|
||||
view_type set(const json_pointer& ptr, V&& value);
|
||||
```
|
||||
|
||||
Only an **editable** document (`#!cpp Editable == true`, e.g. [`json_editable_document`](../json_editable_document.md))
|
||||
has `set`; calling it on a read-only `basic_json_document` fails to compile (`#!cpp static_assert`).
|
||||
|
||||
1. Replaces the value `target` refers to with `value`.
|
||||
2. Sets the member `key` of the object `object` to `value`: assigns it if `object` already has a member with this
|
||||
key -- the first one, should the key occur more than once, and the later duplicates are then dropped (see the
|
||||
[Notes](#notes) below) -- or appends a new member at the end otherwise. A [null](../basic_json_view/is_null.md)
|
||||
`object` first becomes an empty object.
|
||||
3. Assigns `value` to the element at index `idx` of the array `array`, which must already exist (`#!cpp idx <
|
||||
array.size()`).
|
||||
4. Sets the value the JSON pointer `ptr` refers to, relative to [`root()`](root.md), to `value`. The *parent* of the
|
||||
target must already exist: an object member is set as in 2. (added if it does not exist yet), an array element is
|
||||
assigned as in 3., and a last reference token of `#!cpp "-"`, or equal to the size of the array, appends `value`
|
||||
instead, exactly as [`push_back`](push_back.md) would. An empty `ptr` sets [`root()`](root.md) itself, as in 1.
|
||||
|
||||
In every overload, `value` is accepted three ways: a [`basic_json_view`](../basic_json_view/index.md) of *any*
|
||||
document -- read-only or editable, and it does not have to be `target`'s/`object`'s/`array`'s own document -- which
|
||||
is copied so that nothing is shared with the source document afterward; a `BasicJsonType` value; or anything
|
||||
`BasicJsonType` can be constructed from (numbers, strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...).
|
||||
|
||||
## Template parameters
|
||||
|
||||
`V`
|
||||
: the type of `value`, deduced; see above for what is accepted.
|
||||
|
||||
`I`
|
||||
: an integral type other than `#!cpp bool`, deduced (overloads taking a `#!cpp bool` or a non-integral type for
|
||||
`idx` do not participate in overload resolution).
|
||||
|
||||
## Parameters
|
||||
|
||||
`target` (in)
|
||||
: the value to replace
|
||||
|
||||
`object` (in)
|
||||
: the object (or null value) whose member to set
|
||||
|
||||
`array` (in)
|
||||
: the array whose element to assign
|
||||
|
||||
`key` (in)
|
||||
: the key of the member to set
|
||||
|
||||
`idx` (in)
|
||||
: the index of the element to assign; a negative value throws (see [Exceptions](#exceptions))
|
||||
|
||||
`ptr` (in)
|
||||
: a JSON pointer to the value to set, relative to `root()`
|
||||
|
||||
`value` (in)
|
||||
: the new value
|
||||
|
||||
## Return value
|
||||
|
||||
1. a view of `target`, now holding `value`
|
||||
2. a view of the member `key` of `object`, now holding `value`
|
||||
3. a view of the element `idx` of `array`, now holding `value`
|
||||
4. a view of the value `ptr` refers to, now holding `value`
|
||||
|
||||
## Exception safety
|
||||
|
||||
Basic exception safety: `value` is fully encoded -- including the checks below -- into storage owned by the document
|
||||
before anything already reachable from [`root()`](root.md) is touched, so a failure while encoding `value` (an
|
||||
invalid argument, or `#!cpp std::bad_alloc`) leaves the document completely unchanged, other than memory allocated
|
||||
for the encoding that is not reclaimed. A failure of a later allocation -- while an edited array or object switches
|
||||
from its parsed layout to a growable block, see [Notes](#notes) -- can still leave a partial effect, such as a
|
||||
[null](../basic_json_view/is_null.md) `object`/`array` argument already turned into an empty object/array even
|
||||
though `value` itself was not linked in.
|
||||
|
||||
## Exceptions
|
||||
|
||||
1. Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if `value` is a
|
||||
[discarded](../basic_json_view/is_discarded.md) view, or a [discarded](../basic_json/is_discarded.md)
|
||||
`BasicJsonType` value (e.g. `#!cpp BasicJsonType(value_t::discarded)`) -- an object or array `value`, of either
|
||||
kind, is fine and is encoded as a whole subtree.
|
||||
2. Throws [`type_error.305`](../../home/exceptions.md#jsonexceptiontype_error305) if `object` is neither an object
|
||||
nor null -- the same message [`operator[]`](../basic_json_view/operator%5B%5D.md) throws for a string argument on
|
||||
such a value. Throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) if `key` is not
|
||||
valid UTF-8, with the same message [`BasicJsonType::dump()`](../basic_json/dump.md) gives for that string.
|
||||
Also throws what 1. throws for `value`.
|
||||
3. Throws `type_error.305` if `array` is not an array -- the same message `operator[]` throws for a numeric argument
|
||||
on such a value. Throws [`out_of_range.401`](../../home/exceptions.md#jsonexceptionout_of_range401) if `idx` is
|
||||
negative, or if `#!cpp idx >= array.size()`. Also throws what 1. throws for `value`.
|
||||
4. Throws what [`at`](../basic_json_view/at.md) throws (overload 3) for resolving `ptr`'s parent, except that a
|
||||
missing object member or an array index equal to the array's size at the very last reference token is not an
|
||||
error there (it becomes a new member or an appended element) instead of
|
||||
[`out_of_range.403`](../../home/exceptions.md#jsonexceptionout_of_range403)/[`out_of_range.402`](../../home/exceptions.md#jsonexceptionout_of_range402).
|
||||
For the last reference token itself: if the parent is an object (or a primitive value, where it throws
|
||||
`type_error.305`), throws what 2. throws; if the parent is an array, throws what 3. throws for an index that is
|
||||
out of range, or, for a token that is not a valid array index,
|
||||
[`parse_error.106`](../../home/exceptions.md#jsonexceptionparse_error106) (a leading `#!cpp '0'`),
|
||||
[`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) (not a number),
|
||||
[`out_of_range.410`](../../home/exceptions.md#jsonexceptionout_of_range410) (too large for `size_type`), or
|
||||
[`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) (an empty token). Also throws what 1.
|
||||
throws for `value`.
|
||||
|
||||
Every overload also throws [`type_error.319`](../../home/exceptions.md#jsonexceptiontype_error319) if `value` is (or
|
||||
contains) a binary value -- `BasicJsonType` can hold one, but a `json_document` cannot -- and
|
||||
[`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view does not belong to this
|
||||
document") if `target`/`object`/`array` is a [discarded](../basic_json_view/is_discarded.md) view or a view of a
|
||||
*different* document (overloads 1-3 only; overload 4 always starts from this document's own [`root()`](root.md)).
|
||||
|
||||
## Complexity
|
||||
|
||||
1. Linear in the size of `value` (encoding it into the document's storage): constant for a scalar, linear in the
|
||||
number of nested values for an array or object. If `target` is itself an array or object that spans more than one
|
||||
node in its parent's original, unedited layout, and `value` is a scalar, replacing it additionally costs time
|
||||
linear in the number of elements of that parent, the *first* time -- see [Notes](#notes).
|
||||
2. Linear in the number of members of `object`, to find an existing member with `key`, plus the complexity of 1. for
|
||||
`value`.
|
||||
3. Constant, plus the complexity of 1. for `value`.
|
||||
4. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||
that level or the index into the array (as [`at`](../basic_json_view/at.md)), plus the complexity of 2. or 3. for
|
||||
the last token.
|
||||
|
||||
## Notes
|
||||
|
||||
!!! info "Duplicate keys"
|
||||
|
||||
If `object` already has more than one member with `key` (2.), the *first* one is assigned `value` and every
|
||||
later member with the same key is removed -- so that a lookup, an iteration, and
|
||||
[`materialize()`](../basic_json_view/materialize.md) of `object` afterward all agree on a single value for
|
||||
`key`, the same way [`operator[]`](../basic_json_view/operator%5B%5D.md) already picks the first occurrence of a
|
||||
duplicate key for reading. See the [Notes on duplicate keys](../basic_json_view/operator%5B%5D.md#notes) of
|
||||
`operator[]`.
|
||||
|
||||
Setting a member (2.) or an element (3., through 4.) of an array or object whose elements have not been edited
|
||||
before switches it from its parsed layout to a growable block holding links to its elements; a later
|
||||
[`push_back`](push_back.md) or `set` on the same container reuses that block, growing it (amortized constant time)
|
||||
only once it runs out of room. This never moves an element itself -- only where the container's *links* to its
|
||||
elements live -- so a view of an element stays valid, but any iterator already taken over the container is
|
||||
invalidated, since it was walking the old layout. See [Edits](index.md#edits) for what stays valid across an edit in
|
||||
general.
|
||||
|
||||
The same switch happens, for the same reason, when overload 1. replaces a multi-node array/object value with a
|
||||
scalar: the *parent's* element sequence is what has to switch to links, not `target` itself, because the parent
|
||||
originally stepped over `target`'s whole subtree by its node count, which no longer applies once `target` is a
|
||||
one-node scalar.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: (1)/(2)/(3)/(4) replace a value, set a member, assign an element, set via a JSON pointer"
|
||||
|
||||
The example below edits a small configuration document -- replacing a value, adding an object member, assigning
|
||||
an array element, and reaching a field through a JSON pointer -- and shows what
|
||||
[`dump()`](../basic_json_view/dump.md) preserves that is lost once the same edits are made on a `BasicJsonType`
|
||||
value instead: the order object members were written in, and the exact spelling of a number that was never
|
||||
touched.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_document__set.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_document__set.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [push_back](push_back.md) - append to an array
|
||||
- [insert](insert.md) - insert an element into an array
|
||||
- [erase](erase.md) - remove an object member, an array element, or the value a JSON pointer refers to
|
||||
- [root](root.md) - the view of the root value, the starting point of overload 4
|
||||
- [`basic_json_view::dump`](../basic_json_view/dump.md) - serialize the document, keeping an untouched number's
|
||||
spelling with `#!cpp number_format::source`
|
||||
- [Edits](index.md#edits) - what an edit guarantees, for every overload
|
||||
- [Editing a document](../../features/json_view.md#editing-a-document) - why editable documents keep the source
|
||||
text's order and number spelling
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -3,7 +3,7 @@
|
||||
<small>Defined in header `<nlohmann/json_view.hpp>`</small>
|
||||
|
||||
```cpp
|
||||
template<typename BasicJsonType>
|
||||
template<typename BasicJsonType, bool Editable = false>
|
||||
class basic_json_view;
|
||||
```
|
||||
|
||||
@@ -27,16 +27,30 @@ subtree on demand. [`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`
|
||||
[`operator!=`](operator_ne.md) compare two views, or a view and a `BasicJsonType` value, without ever building a
|
||||
`BasicJsonType` value for a view; no ordering comparison (`#!cpp operator<`) is provided.
|
||||
|
||||
`basic_json_view` itself is always read-only -- it never has a `set` or `push_back` of its own. A view of an
|
||||
**editable** document (`#!cpp Editable == true`) sees every edit made through
|
||||
[`basic_json_document::set`](../basic_json_document/set.md) and
|
||||
[`basic_json_document::push_back`](../basic_json_document/push_back.md): once a value is changed, every view that
|
||||
still refers to it -- including ones taken before the change -- reads the new value. See
|
||||
[Edits](../basic_json_document/index.md#edits).
|
||||
|
||||
## Template parameters
|
||||
|
||||
`BasicJsonType`
|
||||
: a specialization of [`basic_json`](../basic_json/index.md), matching the
|
||||
[`basic_json_document`](../basic_json_document/index.md) the view was taken from.
|
||||
|
||||
`Editable`
|
||||
: whether the view is of an editable document, matching the [`basic_json_document`](../basic_json_document/index.md)
|
||||
it was taken from (optional, `#!cpp false` by default). See [Edits](../basic_json_document/index.md#edits).
|
||||
|
||||
## Specializations
|
||||
|
||||
- [**json_view**](../json_view.md) - views of a [`json_document`](../json_document.md)
|
||||
- [**ordered_json_view**](../ordered_json_view.md) - views of an [`ordered_json_document`](../ordered_json_document.md)
|
||||
- [**json_editable_view**](../json_editable_view.md) - views of a [`json_editable_document`](../json_editable_document.md)
|
||||
- [**ordered_json_editable_view**](../ordered_json_editable_view.md) - views of an
|
||||
[`ordered_json_editable_document`](../ordered_json_editable_document.md)
|
||||
|
||||
## Member types
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# <small>nlohmann::</small>json_editable_document
|
||||
|
||||
<small>Defined in header `<nlohmann/json_view.hpp>`</small>
|
||||
|
||||
```cpp
|
||||
using json_editable_document = basic_json_document<json, true>;
|
||||
```
|
||||
|
||||
This type is an **editable** [`basic_json_document`](basic_json_document/index.md) of the default
|
||||
[`json`](json.md) specialization: in addition to everything [`json_document`](json_document.md) offers,
|
||||
[`set`](basic_json_document/set.md) and [`push_back`](basic_json_document/push_back.md) change values after
|
||||
parsing, without ever rewriting the source text -- see [Edits](basic_json_document/index.md#edits) and
|
||||
[Editing a document](../features/json_view.md#editing-a-document).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below patches two fields of a small configuration document -- changing one and adding another --
|
||||
and dumps it back out with the member order and the spelling of the untouched number preserved, something a
|
||||
plain [`json`](json.md) value cannot do.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/json_editable_document.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/json_editable_document.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [json_editable_view](json_editable_view.md) - a view of a value of a `json_editable_document`
|
||||
- [json_document](json_document.md) - the read-only document this type adds edits to
|
||||
- [ordered_json_editable_document](ordered_json_editable_document.md) - the corresponding editable document for
|
||||
`ordered_json`
|
||||
- [Edits](basic_json_document/index.md#edits) - what an edit guarantees
|
||||
|
||||
## Version history
|
||||
|
||||
Since version 3.13.0.
|
||||
@@ -0,0 +1,41 @@
|
||||
# <small>nlohmann::</small>json_editable_view
|
||||
|
||||
<small>Defined in header `<nlohmann/json_view.hpp>`</small>
|
||||
|
||||
```cpp
|
||||
using json_editable_view = basic_json_view<json, true>;
|
||||
```
|
||||
|
||||
This type is a [`basic_json_view`](basic_json_view/index.md) of a value of a
|
||||
[`json_editable_document`](json_editable_document.md). It offers the same read-only interface as
|
||||
[`json_view`](json_view.md); what is different is what it can be a view *of* -- a value that
|
||||
[`set`](basic_json_document/set.md) and [`push_back`](basic_json_document/push_back.md) can change, with every view
|
||||
still referring to it seeing the change, see [Edits](basic_json_document/index.md#edits).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below is the same as [`json_editable_document`'s](json_editable_document.md): every view read back
|
||||
out of the document -- `#!cpp doc.root()` and the views nested under it -- sees the edits made through `set`.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/json_editable_document.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/json_editable_document.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [json_editable_document](json_editable_document.md) - the document type this view refers into
|
||||
- [json_view](json_view.md) - the corresponding read-only view
|
||||
- [ordered_json_editable_view](ordered_json_editable_view.md) - the corresponding view for
|
||||
`ordered_json_editable_document`
|
||||
|
||||
## Version history
|
||||
|
||||
Since 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_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_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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
- 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
|
||||
|
||||
??? example "Example: implicit and explicit conversions"
|
||||
??? example "Example: 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.
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
@@ -83,7 +80,6 @@ inline void from_json(const BasicJsonType& j, type& e);
|
||||
- [Specializing enum conversion](../../features/enum_conversion.md)
|
||||
- [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](./nlohmann_json_serialize_enum_strict.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
|
||||
|
||||
|
||||
@@ -44,9 +44,6 @@ inline void from_json(const BasicJsonType& j, type& e);
|
||||
`"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
|
||||
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
|
||||
|
||||
@@ -102,7 +99,6 @@ inline void from_json(const BasicJsonType& j, type& e);
|
||||
- [Specializing enum conversion](../../features/enum_conversion.md)
|
||||
- [`NLOHMANN_JSON_SERIALIZE_ENUM`](./nlohmann_json_serialize_enum.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
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# <small>nlohmann::</small>ordered_json_editable_document
|
||||
|
||||
<small>Defined in header `<nlohmann/json_view.hpp>`</small>
|
||||
|
||||
```cpp
|
||||
using ordered_json_editable_document = basic_json_document<ordered_json, true>;
|
||||
```
|
||||
|
||||
This type is an **editable** [`basic_json_document`](basic_json_document/index.md) of the
|
||||
[`ordered_json`](ordered_json.md) specialization: [`set`](basic_json_document/set.md) and
|
||||
[`push_back`](basic_json_document/push_back.md) change values after parsing, as for
|
||||
[`json_editable_document`](json_editable_document.md), and
|
||||
[`materialize()`](basic_json_view/materialize.md) preserves the document order of object members -- including
|
||||
members [`set`](basic_json_document/set.md) added -- instead of sorting them like
|
||||
[`json_editable_document`](json_editable_document.md) does.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below edits a document with `set`, then shows that `materialize()` keeps the member order of the
|
||||
source text (with the new member at the end) for `ordered_json_editable_document`, where it would sort the
|
||||
members alphabetically for [`json_editable_document`](json_editable_document.md).
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/ordered_json_editable_document.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/ordered_json_editable_document.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [ordered_json_editable_view](ordered_json_editable_view.md) - a view of a value of an
|
||||
`ordered_json_editable_document`
|
||||
- [ordered_json_document](ordered_json_document.md) - the read-only document this type adds edits to
|
||||
- [json_editable_document](json_editable_document.md) - the corresponding editable document for the default `json`
|
||||
specialization
|
||||
- [Object Order](../features/object_order.md)
|
||||
- [Edits](basic_json_document/index.md#edits) - what an edit guarantees
|
||||
|
||||
## Version history
|
||||
|
||||
Since version 3.13.0.
|
||||
@@ -0,0 +1,40 @@
|
||||
# <small>nlohmann::</small>ordered_json_editable_view
|
||||
|
||||
<small>Defined in header `<nlohmann/json_view.hpp>`</small>
|
||||
|
||||
```cpp
|
||||
using ordered_json_editable_view = basic_json_view<ordered_json, true>;
|
||||
```
|
||||
|
||||
This type is a [`basic_json_view`](basic_json_view/index.md) of a value of an
|
||||
[`ordered_json_editable_document`](ordered_json_editable_document.md), the corresponding view for
|
||||
[`ordered_json_view`](ordered_json_view.md) the way [`json_editable_view`](json_editable_view.md) is for
|
||||
[`json_view`](json_view.md).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below is the same as [`ordered_json_editable_document`'s](ordered_json_editable_document.md): the
|
||||
views `set` returns see the document's member order preserved on `materialize()`, unlike for a
|
||||
[`json_editable_document`](json_editable_document.md).
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/ordered_json_editable_document.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/ordered_json_editable_document.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [ordered_json_editable_document](ordered_json_editable_document.md) - the document type this view refers into
|
||||
- [ordered_json_view](ordered_json_view.md) - the corresponding read-only view
|
||||
- [json_editable_view](json_editable_view.md) - the corresponding view for `json_editable_document`
|
||||
|
||||
## Version history
|
||||
|
||||
Since version 3.13.0.
|
||||
@@ -21,18 +21,17 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
||||
|
||||
| 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.1 | 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.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.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.5.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 14.2.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 |
|
||||
| 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 |
|
||||
|
||||
@@ -64,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_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_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:
|
||||
|
||||
@@ -77,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_STRICT_NUL_HANDLING 1
|
||||
#define JSON_STRICT_BINARY_UTF8 1
|
||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1
|
||||
#define JSON_DELETE_DEPRECATED_FUNCTIONS 1
|
||||
#include <nlohmann/json.hpp>
|
||||
```
|
||||
|
||||
@@ -88,13 +84,8 @@ way to achieve this.
|
||||
### Removal of deprecated functions
|
||||
|
||||
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
|
||||
`1` turns these warnings into errors, as the deprecated functions are then deleted. The
|
||||
[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.
|
||||
code still uses. The [migration guide](../integration/migration_guide.md#replace-deprecated-functions) shows how to
|
||||
replace each of them.
|
||||
|
||||
| Deprecated | Since | Migration |
|
||||
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------------------------------------------------------------------------------|
|
||||
@@ -106,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`](../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) |
|
||||
| [`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.
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using json_editable_document = nlohmann::json_editable_document;
|
||||
using json_editable_view = nlohmann::json_editable_view;
|
||||
|
||||
int main()
|
||||
{
|
||||
// a deprecated field is dropped from a configuration file, and a
|
||||
// decommissioned replica is removed from the list -- "price" keeps its
|
||||
// trailing zero, and the fields around the removed ones keep their order
|
||||
const std::string text = R"({
|
||||
"name": "cache",
|
||||
"legacy_host": "db0",
|
||||
"host": "db1",
|
||||
"price": 19.90,
|
||||
"replicas": ["db2", "db3", "db4"]
|
||||
})";
|
||||
|
||||
json_editable_document doc = json_editable_document::parse(text);
|
||||
|
||||
doc.erase(doc.root(), "legacy_host"); // (1) an object member
|
||||
doc.erase(doc.root()["replicas"], 1); // (2) an array element ("db3")
|
||||
const std::size_t removed = doc.erase(json::json_pointer("/replicas/0")); // (3) via a JSON pointer
|
||||
|
||||
std::cout << removed << '\n';
|
||||
std::cout << doc.root().dump(2, ' ', false, json_editable_view::number_format::source) << "\n\n";
|
||||
|
||||
// the same edits on a plain json value: object_t is a std::map, so
|
||||
// parsing already sorted the keys, and dump() rewrites every number to
|
||||
// its shortest form, even "price", which was never touched
|
||||
json plain = json::parse(text);
|
||||
plain.erase("legacy_host");
|
||||
plain["replicas"].erase(1);
|
||||
plain["replicas"].erase(0);
|
||||
std::cout << plain.dump(2) << '\n';
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
1
|
||||
{
|
||||
"name": "cache",
|
||||
"host": "db1",
|
||||
"price": 19.90,
|
||||
"replicas": [
|
||||
"db4"
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
"host": "db1",
|
||||
"name": "cache",
|
||||
"price": 19.9,
|
||||
"replicas": [
|
||||
"db4"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using json_editable_document = nlohmann::json_editable_document;
|
||||
using json_editable_view = nlohmann::json_editable_view;
|
||||
|
||||
int main()
|
||||
{
|
||||
// a deployment plan -- "budget" is written with a trailing zero that has
|
||||
// no effect on its value
|
||||
const std::string text = R"({
|
||||
"release": "2026.09",
|
||||
"steps": ["build", "test", "deploy"],
|
||||
"budget": 19.90
|
||||
})";
|
||||
|
||||
json_editable_document doc = json_editable_document::parse(text);
|
||||
|
||||
const std::size_t deploy_index = 2;
|
||||
const auto deploy = doc.root()["steps"][deploy_index]; // held across the insert
|
||||
|
||||
doc.insert(doc.root()["steps"], deploy_index, "smoke-test"); // insert before "deploy"
|
||||
|
||||
// the held view still refers to "deploy", even though its index moved
|
||||
// from 2 to 3, and nothing else in the document was touched
|
||||
std::cout << deploy.dump() << '\n';
|
||||
std::cout << doc.root().dump(2, ' ', false, json_editable_view::number_format::source) << "\n\n";
|
||||
|
||||
// the same edit on a plain json value: an index held from before the
|
||||
// insert now refers to whatever moved into that slot, and dump()
|
||||
// rewrites "budget" to its shortest form even though it was never
|
||||
// touched
|
||||
json plain = json::parse(text);
|
||||
plain["steps"].insert(plain["steps"].begin() + static_cast<std::ptrdiff_t>(deploy_index), "smoke-test");
|
||||
std::cout << plain["steps"][deploy_index].dump() << '\n';
|
||||
std::cout << plain.dump(2) << '\n';
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
"deploy"
|
||||
{
|
||||
"release": "2026.09",
|
||||
"steps": [
|
||||
"build",
|
||||
"test",
|
||||
"smoke-test",
|
||||
"deploy"
|
||||
],
|
||||
"budget": 19.90
|
||||
}
|
||||
|
||||
"smoke-test"
|
||||
{
|
||||
"budget": 19.9,
|
||||
"release": "2026.09",
|
||||
"steps": [
|
||||
"build",
|
||||
"test",
|
||||
"smoke-test",
|
||||
"deploy"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using json_document = nlohmann::json_document;
|
||||
using image_check = json_document::image_check;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << std::boolalpha;
|
||||
|
||||
// the image of a parsed document -- as if read back from a cache file or
|
||||
// received from another process running the same build of the library
|
||||
const std::string text = R"({"name": "cache", "note": "caf\u00e9", "replicas": ["db2", "db3"]})";
|
||||
const json_document parsed = json_document::parse(text);
|
||||
const std::vector<std::uint8_t> image = parsed.save();
|
||||
|
||||
// (1)/(2) load() needs no parsing, yet dumps exactly what parsing did
|
||||
const json_document borrowed = json_document::load(image);
|
||||
std::cout << (borrowed.root().dump() == parsed.root().dump()) << '\n';
|
||||
std::cout << borrowed.owns_source() << '\n'; // borrowed: still points into `image`
|
||||
|
||||
// (3) load(std::move(image)) keeps the vector instead of copying it
|
||||
std::vector<std::uint8_t> to_move = image;
|
||||
const json_document owned = json_document::load(std::move(to_move));
|
||||
std::cout << owned.owns_source() << '\n';
|
||||
|
||||
// a damaged image -- the last byte of the decoded string "note" holds
|
||||
// (an escape sequence, so it was unescaped into the document's own
|
||||
// buffer), flipped, as storage or transport corruption might do
|
||||
std::vector<std::uint8_t> damaged = image;
|
||||
damaged[damaged.size() - 2] = 0xFF;
|
||||
|
||||
// image_check::full inspects strings and numbers, so it catches the damage
|
||||
try
|
||||
{
|
||||
static_cast<void>(json_document::load(damaged, image_check::full));
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
std::cout << e.id << '\n';
|
||||
}
|
||||
|
||||
// image_check::bounds only checks structure and bounds, so a cache the
|
||||
// process already trusts loads without the extra scan -- reading a value
|
||||
// the damage did not touch is still safe
|
||||
const json_document trusted = json_document::load(damaged, image_check::bounds);
|
||||
std::cout << trusted.root()["name"].get<std::string>() << '\n';
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
true
|
||||
false
|
||||
true
|
||||
116
|
||||
cache
|
||||
@@ -0,0 +1,24 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using json_editable_document = nlohmann::json_editable_document;
|
||||
|
||||
int main()
|
||||
{
|
||||
// "events" starts out null -- the first push_back() turns it into an
|
||||
// array, exactly like set() turns a null object member into an object
|
||||
json_editable_document doc = json_editable_document::parse(R"({"source": "sensor-1", "events": null})");
|
||||
|
||||
const auto first = doc.push_back(doc.root()["events"], json{{"type", "start"}, {"t", 0}});
|
||||
for (int t = 1; t <= 3; ++t)
|
||||
{
|
||||
doc.push_back(doc.root()["events"], json{{"type", "tick"}, {"t", t}});
|
||||
}
|
||||
|
||||
// push_back() never moves an existing element: a view taken from an
|
||||
// earlier call still refers to the same element after later ones
|
||||
std::cout << first.dump() << '\n';
|
||||
std::cout << doc.root()["events"].size() << '\n';
|
||||
std::cout << doc.root().dump(2) << '\n';
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{"t":0,"type":"start"}
|
||||
4
|
||||
{
|
||||
"source": "sensor-1",
|
||||
"events": [
|
||||
{
|
||||
"t": 0,
|
||||
"type": "start"
|
||||
},
|
||||
{
|
||||
"t": 1,
|
||||
"type": "tick"
|
||||
},
|
||||
{
|
||||
"t": 2,
|
||||
"type": "tick"
|
||||
},
|
||||
{
|
||||
"t": 3,
|
||||
"type": "tick"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json_document = nlohmann::json_document;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << std::boolalpha;
|
||||
|
||||
// a configuration a service parses once and then caches as an image, so
|
||||
// that later requests can load() it instead of parsing the text again
|
||||
const std::string text = R"({"name": "cache", "host": "db1", "port": 6379, "replicas": ["db2", "db3"]})";
|
||||
const json_document config = json_document::parse(text);
|
||||
|
||||
// save() turns the parsed document into a byte buffer: a 64-byte header,
|
||||
// the node index, the source text, and the decoded strings
|
||||
const std::vector<std::uint8_t> image = config.save();
|
||||
std::cout << image.size() << '\n';
|
||||
|
||||
// the same document always saves to the same bytes
|
||||
std::cout << (image == json_document::parse(text).save()) << '\n';
|
||||
|
||||
// loading the image back needs no parsing, yet dumps exactly what
|
||||
// parsing the text produced
|
||||
const json_document reloaded = json_document::load(image);
|
||||
std::cout << (reloaded.root().dump() == config.root().dump()) << '\n';
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
316
|
||||
true
|
||||
true
|
||||
@@ -0,0 +1,41 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using json_editable_document = nlohmann::json_editable_document;
|
||||
using json_editable_view = nlohmann::json_editable_view;
|
||||
|
||||
int main()
|
||||
{
|
||||
// a configuration file, as it might be read from disk -- "price" is
|
||||
// written with a trailing zero that has no effect on its value
|
||||
const std::string text = R"({
|
||||
"name": "cache",
|
||||
"host": "db1",
|
||||
"port": 6379,
|
||||
"price": 19.90,
|
||||
"replicas": ["db2", "db3"],
|
||||
"timeout": 30
|
||||
})";
|
||||
|
||||
json_editable_document doc = json_editable_document::parse(text);
|
||||
|
||||
doc.set(doc.root()["port"], 6380); // (1) replace a value
|
||||
doc.set(doc.root(), "region", "us-east"); // (2) add a member
|
||||
doc.set(doc.root()["replicas"], 0, "db4"); // (3) assign an element
|
||||
doc.set(json::json_pointer("/timeout"), 45); // (4) via a JSON pointer
|
||||
|
||||
// members stay in document order (the new one at the end), and a number
|
||||
// that was not itself edited keeps its exact spelling
|
||||
std::cout << doc.root().dump(2, ' ', false, json_editable_view::number_format::source) << "\n\n";
|
||||
|
||||
// the same edits on a plain json value: object_t is a std::map, so
|
||||
// parsing already sorted the keys, and dump() rewrites every number to
|
||||
// its shortest form, even "price", which was never touched
|
||||
json plain = json::parse(text);
|
||||
plain["port"] = 6380;
|
||||
plain["region"] = "us-east";
|
||||
plain["replicas"][0] = "db4";
|
||||
plain[json::json_pointer("/timeout")] = 45;
|
||||
std::cout << plain.dump(2) << '\n';
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "cache",
|
||||
"host": "db1",
|
||||
"port": 6380,
|
||||
"price": 19.90,
|
||||
"replicas": [
|
||||
"db4",
|
||||
"db3"
|
||||
],
|
||||
"timeout": 45,
|
||||
"region": "us-east"
|
||||
}
|
||||
|
||||
{
|
||||
"host": "db1",
|
||||
"name": "cache",
|
||||
"port": 6380,
|
||||
"price": 19.9,
|
||||
"region": "us-east",
|
||||
"replicas": [
|
||||
"db4",
|
||||
"db3"
|
||||
],
|
||||
"timeout": 45
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
using json_editable_document = nlohmann::json_editable_document;
|
||||
using json_editable_view = nlohmann::json_editable_view;
|
||||
|
||||
int main()
|
||||
{
|
||||
// a configuration file, as it might be read from disk
|
||||
const std::string text = R"({"name": "cache", "host": "db1", "port": 6379, "price": 19.90})";
|
||||
std::cout << text << "\n\n";
|
||||
|
||||
// patch two fields -- "price" is never touched
|
||||
json_editable_document doc = json_editable_document::parse(text);
|
||||
doc.set(doc.root(), "host", "db2");
|
||||
doc.set(doc.root(), "retries", 3);
|
||||
|
||||
// member order (the new member at the end) and the untouched number's
|
||||
// exact spelling survive
|
||||
std::cout << doc.root().dump(-1, ' ', false, json_editable_view::number_format::source) << '\n';
|
||||
|
||||
// the same patch on a plain json value: keys are sorted (object_t is a
|
||||
// std::map), and "price" is rewritten even though the patch never
|
||||
// touched it
|
||||
json plain = json::parse(text);
|
||||
plain["host"] = "db2";
|
||||
plain["retries"] = 3;
|
||||
std::cout << plain.dump() << '\n';
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{"name": "cache", "host": "db1", "port": 6379, "price": 19.90}
|
||||
|
||||
{"name":"cache","host":"db2","port":6379,"price":19.90,"retries":3}
|
||||
{"host":"db2","name":"cache","port":6379,"price":19.9,"retries":3}
|
||||
@@ -0,0 +1,15 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
using ordered_json_editable_document = nlohmann::ordered_json_editable_document;
|
||||
|
||||
int main()
|
||||
{
|
||||
// ordered_json_editable_document is basic_json_document<nlohmann::ordered_json, true>
|
||||
ordered_json_editable_document doc = ordered_json_editable_document::parse(R"({"z": 1, "a": 2, "m": 3})");
|
||||
doc.set(doc.root(), "b", 4); // set() always appends a new member at the end
|
||||
|
||||
// materialize() preserves the document order (with "b" at the end),
|
||||
// instead of sorting the keys the way json_editable_document does
|
||||
std::cout << doc.root().materialize().dump() << '\n';
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"z":1,"a":2,"m":3,"b":4}
|
||||
@@ -168,9 +168,9 @@ The library maps CBOR types to JSON value types as follows:
|
||||
!!! 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
|
||||
result to fit into `number_integer_t` (`std::int64_t` by default), the result is stored as `number_float_t`, like
|
||||
a too small integer in JSON text. For example, `-18446744073709551616` (`0x3B` followed by eight `0xFF` bytes) is
|
||||
stored as `-1.8446744073709552e+19`.
|
||||
result to fit into `number_integer_t` (`std::int64_t` by default), parsing fails with a
|
||||
[`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) exception rather than overflowing
|
||||
silently.
|
||||
|
||||
!!! warning "Object keys"
|
||||
|
||||
|
||||
@@ -58,23 +58,6 @@ assert(jPi.get<TaskState>() == TS_INVALID );
|
||||
--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
|
||||
|
||||
Just as in [Arbitrary Type Conversions](arbitrary_types.md) above,
|
||||
|
||||
@@ -14,6 +14,8 @@ C++ types, and finally serialize it again.
|
||||
[parsing untrusted input](parsing/untrusted_input.md).
|
||||
- [Zero-copy JSON views](json_view.md) — read a JSON text through a flat index instead of building a `json` tree;
|
||||
strings and numbers stay in the input and are only decoded when needed.
|
||||
[Editable documents](json_view.md#editing-a-document) can also be modified, and [images](json_view.md#images) load a
|
||||
parsed document again without parsing it.
|
||||
- [Comments](comments.md) and [trailing commas](trailing_commas.md) — opt-in relaxations of the JSON grammar.
|
||||
|
||||
## Accessing and modifying values
|
||||
|
||||
@@ -187,14 +187,129 @@ a number to its parsed `#!cpp double`/`#!cpp int64_t` value.
|
||||
[`operator<<`](../api/basic_json_view/operator_ltlt.md) writes a view to a stream the way `basic_json`'s does, using
|
||||
the stream's `width`/`fill` for indentation.
|
||||
|
||||
## Editing a document
|
||||
|
||||
Everything above is read-only: a `json_document`/`json_view` lets you look at a parsed text without copying it, but
|
||||
not change it. [`basic_json_document<BasicJsonType, true>`](../api/basic_json_document/index.md) -- more conveniently
|
||||
spelled [`json_editable_document`](../api/json_editable_document.md) or
|
||||
[`ordered_json_editable_document`](../api/ordered_json_editable_document.md) -- also lets you
|
||||
[`set`](../api/basic_json_document/set.md) a value, [`push_back`](../api/basic_json_document/push_back.md) onto or
|
||||
[`insert`](../api/basic_json_document/insert.md) into an array, and [`erase`](../api/basic_json_document/erase.md)
|
||||
an object member or an array element, still without ever building a `basic_json` tree for parts you do not touch.
|
||||
|
||||
`#!cpp Editable` defaults to `#!cpp false`, so `json_document`/`ordered_json_document` are unaffected -- they carry
|
||||
none of the bookkeeping edits need, and calling `set`/`push_back`/`insert`/`erase` on one is a compile error, not a
|
||||
runtime one.
|
||||
|
||||
### Why: editing without reformatting
|
||||
|
||||
The `#!cpp 19.90` price from [above](#writing-a-view-back) is exactly the kind of value that makes editing a `json`
|
||||
or `ordered_json` value in place lossy. Say you parse a configuration file, patch one field, and write it back:
|
||||
|
||||
- **`json`** re-sorts every key on the way in (`object_t` is a `#!cpp std::map`) and rewrites every number to its
|
||||
shortest round-trip form on the way out -- a one-field patch turns into a diff that reorders the whole file and
|
||||
rewrites `#!cpp 19.90` to `#!cpp 19.9`.
|
||||
- **`ordered_json`** keeps the key order, but still rewrites every number the same way: parsing has already reduced
|
||||
it to a `#!cpp double`/`#!cpp int64_t`, and there is no way back to how it was spelled in the source text.
|
||||
|
||||
An editable document keeps both. [`dump()`](../api/basic_json_view/dump.md) of an edited document writes members in
|
||||
document order -- a member [`set`](../api/basic_json_document/set.md) added goes at the end, exactly where it was
|
||||
inserted, and an [`erase`](../api/basic_json_document/erase.md)d member simply leaves a gap: everything around it
|
||||
keeps its place -- and [`number_format::source`](../api/basic_json_view/number_format.md) keeps the exact spelling
|
||||
of every number an edit did not itself touch; a number an edit *did* touch is written the way
|
||||
[`BasicJsonType::dump()`](../api/basic_json/dump.md) would write it, since there is no source spelling for a brand
|
||||
new value.
|
||||
|
||||
??? example "Example: patch a configuration, keeping member order and number spellings"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/json_editable_document.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/json_editable_document.output"
|
||||
```
|
||||
|
||||
### What stays valid, and what an edit costs
|
||||
|
||||
The source text itself is **never written**, and the parsed index never moves -- a value keeps the node it was
|
||||
parsed into for as long as it is not itself replaced. So every [view](../api/basic_json_view/index.md) taken before
|
||||
an edit, including a previously obtained [`root()`](../api/basic_json_document/root.md), stays valid and, if it
|
||||
still refers to the edited value, sees the edit; a view of a value a later edit drops or replaces just keeps showing
|
||||
what it last held. New values go to storage the document allocates and owns on demand. The one thing an edit does
|
||||
invalidate is the **iterators** taken over an edited array or object: the first time one of its elements is set,
|
||||
appended to, inserted into, or erased, its elements move from the parsed, fixed layout to a growable block of links
|
||||
so that [`push_back`](../api/basic_json_document/push_back.md) can later grow it in amortized constant time --
|
||||
existing elements are not touched, but an iterator that was walking the old layout no longer matches. A string
|
||||
obtained with
|
||||
[`get_string()`](../api/basic_json_view/get_string.md) is unaffected either way and stays valid across further
|
||||
edits. See [`basic_json_document`'s Edits](../api/basic_json_document/index.md#edits) for the details, and
|
||||
[`set`'s Exception safety](../api/basic_json_document/set.md#exception-safety) for what an edit guarantees if it
|
||||
throws (the *basic* guarantee, not the strong one `dump()` and the read-only functions provide). How edits are kept in
|
||||
the index is described in the [architecture overview](../home/architecture.md#node-index-of-json-views).
|
||||
|
||||
## Images
|
||||
|
||||
[`save()`](../api/basic_json_document/save.md) writes a document as an *image*: a byte buffer that
|
||||
[`load()`](../api/basic_json_document/load.md) reads back into a document without parsing -- no lexing, no building
|
||||
the node index, nothing but copying the nodes and pointing the text and the decoded strings at the image. Where
|
||||
[`parse_copy()`](../api/basic_json_document/parse_copy.md) still has to scan the whole input,
|
||||
[`load()`](../api/basic_json_document/load.md) turns that scan into a copy of the node index alone.
|
||||
|
||||
**Why.** A document that is parsed once and then read many times -- a configuration loaded at startup, a template
|
||||
rendered on every request, a large reference dataset a worker process needs in memory -- pays for parsing once but
|
||||
can amortize [`save()`](../api/basic_json_document/save.md)'s cost across every later load. That makes images useful
|
||||
for a cache: save a document the first time it is parsed (to a file, a shared-memory segment, an in-process cache),
|
||||
and [`load()`](../api/basic_json_document/load.md) it on every later use instead of parsing the source text again.
|
||||
They are just as useful for handing a parsed document to another process (or a forked worker) running the same build
|
||||
of the library, since [`load()`](../api/basic_json_document/load.md) turns the transfer into a copy of the node index
|
||||
plus pointers into the received bytes, not a re-parse.
|
||||
|
||||
**Choosing a check.** [`load()`](../api/basic_json_document/load.md) takes an
|
||||
[`image_check`](../api/basic_json_document/load.md#image_check) that trades validation against speed:
|
||||
`image_check::full` (the default) checks everything the parser itself guarantees, so a checked image is exactly as
|
||||
safe to read and serialize as a freshly parsed document -- the right choice whenever the image did not come straight
|
||||
from this process's own [`save()`](../api/basic_json_document/save.md), such as a file or a network peer.
|
||||
`image_check::bounds` only checks structure and bounds -- cheaper, since it skips scanning the text and the decoded
|
||||
strings -- and fits a cache the process trusts, one it wrote and reads back itself. `image_check::none` skips
|
||||
validation entirely, for an image trusted as much as the process's own memory. See
|
||||
[`load()`'s Notes](../api/basic_json_document/load.md#notes) for exactly what each level does and does not guarantee.
|
||||
|
||||
??? example "Example: cache a parsed configuration as an image"
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_document__save.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/basic_json_document__save.output"
|
||||
```
|
||||
|
||||
!!! warning "Experimental"
|
||||
|
||||
The image format is versioned but not yet stable, and may change in an incompatible way before it is declared
|
||||
stable. It is little-endian only, and tied to the library build that wrote it -- use it to cache a document or to
|
||||
hand one to another process running the *same* build, not as a long-term storage format; keep the original JSON
|
||||
text if a saved document needs to be readable by a future library version.
|
||||
|
||||
The idea of a document you can read without parsing comes from zero-copy formats such as
|
||||
[FlatBuffers](https://github.com/google/flatbuffers) and [YaFF](https://github.com/yandex/yaff); the check
|
||||
[`load()`](../api/basic_json_document/load.md) runs follows the idea of FlatBuffers' Verifier. No code is taken from
|
||||
either.
|
||||
|
||||
## Choosing between `json`, `ordered_json`, the SAX interface, and `json_view`
|
||||
|
||||
| | [`json`](../api/json.md) / [`ordered_json`](../api/ordered_json.md) | [SAX interface](parsing/sax_interface.md) | [`json_document`](../api/json_document.md) / [`json_view`](../api/json_view.md) |
|
||||
|---|---|---|---|
|
||||
| **Ownership** | owns every value | owns nothing; you decide what to keep, in your handler | borrows or owns the *text*; the index is always owned by the document |
|
||||
| **Mutability** | freely mutable | not applicable (a one-shot event stream) | read-only |
|
||||
| **What you get** | a full tree you can read, write, and keep as long as you like | a sequence of callbacks; whatever your handler builds from them | a flat index plus, on demand, [`materialize()`](../api/basic_json_view/materialize.md)d `json`/`ordered_json` values for the parts you actually use |
|
||||
| **Typical use** | general-purpose JSON handling: config, request/response bodies you build or modify, anything you hold onto | validating or projecting a text into your own data structure without ever holding the whole thing as JSON | large or high-volume input where you only need part of it, or need it repeatedly, and can keep the source text (or a copy) alive for as long as the document lives |
|
||||
| | [`json`](../api/json.md) / [`ordered_json`](../api/ordered_json.md) | [SAX interface](parsing/sax_interface.md) | [`json_document`](../api/json_document.md) / [`json_view`](../api/json_view.md) | [`json_editable_document`](../api/json_editable_document.md) / [`json_editable_view`](../api/json_editable_view.md) |
|
||||
|---|---|---|---|---|
|
||||
| **Ownership** | owns every value | owns nothing; you decide what to keep, in your handler | borrows or owns the *text*; the index is always owned by the document | same as `json_document`; edits go to storage the document owns |
|
||||
| **Mutability** | freely mutable | not applicable (a one-shot event stream) | read-only | [`set`](../api/basic_json_document/set.md)/[`push_back`](../api/basic_json_document/push_back.md)/[`insert`](../api/basic_json_document/insert.md)/[`erase`](../api/basic_json_document/erase.md) edit in place; the source text is never rewritten |
|
||||
| **What you get** | a full tree you can read, write, and keep as long as you like | a sequence of callbacks; whatever your handler builds from them | a flat index plus, on demand, [`materialize()`](../api/basic_json_view/materialize.md)d `json`/`ordered_json` values for the parts you actually use | the same, plus [`dump()`](../api/basic_json_view/dump.md) of an edited document that keeps the member order and, with [`number_format::source`](../api/basic_json_view/number_format.md), the spelling of every untouched number |
|
||||
| **Typical use** | general-purpose JSON handling: config, request/response bodies you build or modify, anything you hold onto | validating or projecting a text into your own data structure without ever holding the whole thing as JSON | large or high-volume input where you only need part of it, or need it repeatedly, and can keep the source text (or a copy) alive for as long as the document lives | a document you read, patch a few fields of, and write back -- a configuration file, for instance -- where the rest of it should come back exactly as it was |
|
||||
| **Caching/reload** | not applicable -- re-parse, or roll your own serialization | not applicable | [`save()`](../api/basic_json_document/save.md)/[`load()`](../api/basic_json_document/load.md): cache the parsed index as an image and reload it without parsing | same, saving the document's current -- possibly edited -- state |
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
## `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`
|
||||
|
||||
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`
|
||||
|
||||
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
|
||||
is planned to become the default in version 4.0.0.
|
||||
value, such as the result of `std::forward_as_tuple(j)`. This lets `std::tuple` convert such tuples element-wise.
|
||||
|
||||
See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md).
|
||||
|
||||
@@ -210,13 +198,6 @@ default.
|
||||
|
||||
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`
|
||||
|
||||
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_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_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
|
||||
underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component)
|
||||
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
|
||||
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
|
||||
[`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
|
||||
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).
|
||||
- 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
|
||||
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
|
||||
- [Parsing](parsing/index.md) - the available parsing functions and inputs
|
||||
- [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
|
||||
- [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
|
||||
|
||||
@@ -194,8 +194,8 @@ packet-beta
|
||||
|
||||
| Bytes | Field | Type | Contents |
|
||||
|-------|---------|------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float |
|
||||
| 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes are (0: the source text, 1: the buffer of decoded strings, for strings with escapes); bit 2: the value of a boolean |
|
||||
| 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float; 10 for a link (see below) |
|
||||
| 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes (or a number's token) are (0: the source text, 1: the buffer of decoded strings, for strings with escapes, 2: the edit buffer); bit 2: the value of a boolean; bits 3 and 4: moved and new (see below) |
|
||||
| 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; objects: the number of their hash index (1-based), or 0; otherwise 0 |
|
||||
| 4-7 | `off` | `uint32_t` | where the value starts: the first byte after a string's opening quote (or its position in the buffer of decoded strings), the first byte of a number or literal, the bracket of an array or object |
|
||||
| 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements |
|
||||
@@ -244,6 +244,29 @@ flowchart LR
|
||||
All `flags` are 0. The integer's bytes 8-15 hold its value, 1; its `extra` says it has one digit. The float's `extra`
|
||||
says it has one integer and one fraction digit, and its `len` is that of the token `2.5`.
|
||||
|
||||
Editable documents ([`json_editable_document`](../api/json_editable_document.md),
|
||||
[`detail/view/edit.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/edit.hpp) and
|
||||
[`detail/view/edit_storage.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/edit_storage.hpp))
|
||||
never write the source text and never move or resize the parsed index, so views stay valid while the document is
|
||||
edited:
|
||||
|
||||
- A new scalar is written over its node. Its text (a string, or the token of a number as `dump()` writes it) goes to
|
||||
the edit buffer, which `flags` bits 0-1 then name.
|
||||
- An array or object whose elements change gets the flag *moved* (bit 3): its elements then live in a separate
|
||||
sequence (a header node, then the entries), whose number is in `off`. The entries are links (`kind` 10), whose bytes
|
||||
8-15 hold the address of the value's node, so values never move.
|
||||
- A node written by an edit gets the flag *new* (bit 4): it has no position in the source text.
|
||||
- Views of read-only documents compile without any of this: how views walk the index is a template parameter
|
||||
(`navigation<Editable>`).
|
||||
|
||||
Images ([`save`](../api/basic_json_document/save.md) and [`load`](../api/basic_json_document/load.md),
|
||||
[`detail/view/image.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/image.hpp)) store
|
||||
the nodes as they are: a 64-byte header (the magic bytes `NJVI`, a format version, the sizes, and reserved bytes that
|
||||
must be zero), the nodes, the text, and the decoded strings. An edited document is first written in document order, as
|
||||
the parser would have written it (without links), and the numbers of the hash indexes are cleared, since `load`
|
||||
rebuilds the indexes. So a change of the node layout is a change of the image format: it must raise `image_version`,
|
||||
and `load` then rejects images of other versions (`parse_error.116`) instead of misreading them.
|
||||
|
||||
## Input adapters
|
||||
|
||||
Input is read via **input adapters** that abstract a source. Every input adapter provides this interface:
|
||||
|
||||
@@ -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 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)
|
||||
```
|
||||
|
||||
@@ -388,6 +391,23 @@ A UBJSON high-precision number could not be parsed.
|
||||
[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A
|
||||
```
|
||||
|
||||
### json.exception.parse_error.116
|
||||
|
||||
[`basic_json_document::load()`](../api/basic_json_document/load.md) rejected an
|
||||
[image](../features/json_view.md#images): either the bytes are not one [`save()`](../api/basic_json_document/save.md)
|
||||
could have written (too short, an unknown magic number or format version, or sizes that do not fit the buffer), or
|
||||
they are, but fail the requested [`image_check`](../api/basic_json_document/load.md#image_check).
|
||||
|
||||
!!! failure "Example message"
|
||||
|
||||
```
|
||||
[json.exception.parse_error.116] parse error: invalid json_document image: the check failed
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
This exception was added in version 3.13.0, together with [images](../features/json_view.md#images).
|
||||
|
||||
## Iterator errors
|
||||
|
||||
This exception is thrown if iterators passed to a library function do not match
|
||||
@@ -596,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
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
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 &`.
|
||||
@@ -791,32 +808,43 @@ 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}`
|
||||
|
||||
### json.exception.type_error.318
|
||||
### json.exception.type_error.319
|
||||
|
||||
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.
|
||||
[`basic_json_document::set`](../api/basic_json_document/set.md) and
|
||||
[`basic_json_document::push_back`](../api/basic_json_document/push_back.md) can store any `basic_json` value except
|
||||
a binary one: a `json_document` has no representation for [binary values](../features/binary_values.md), which only
|
||||
ever arise from parsing a binary format or from an explicit [`json::binary`](../api/basic_json/binary.md) value, not
|
||||
from JSON text.
|
||||
|
||||
!!! failure "Example message"
|
||||
|
||||
```
|
||||
[json.exception.type_error.318] duplicate object key 'red'
|
||||
[json.exception.type_error.319] cannot store a binary value in a json_document
|
||||
```
|
||||
|
||||
### json.exception.type_error.321
|
||||
!!! note
|
||||
|
||||
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.
|
||||
This exception was added in version 3.13.0, together with editable [`json_document`s](../features/json_view.md).
|
||||
|
||||
!!! failure "Example message"
|
||||
### json.exception.type_error.320
|
||||
|
||||
[`basic_json_document::save()`](../api/basic_json_document/save.md) cannot write an
|
||||
[image](../features/json_view.md#images) of a [discarded](../api/basic_json_document/is_discarded.md) document.
|
||||
[`save()`](../api/basic_json_document/save.md) and [`load()`](../api/basic_json_document/load.md) also throw this
|
||||
exception on a big-endian target, since the image format is little-endian only.
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
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
|
||||
[json.exception.type_error.320] cannot save a discarded json_document
|
||||
```
|
||||
```
|
||||
[json.exception.type_error.320] json_document images need a little-endian target
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
This exception was added in version 3.13.0, together with [images](../features/json_view.md#images).
|
||||
|
||||
## Out of range
|
||||
|
||||
@@ -890,18 +918,13 @@ The JSON Patch operations 'remove' and 'add' cannot be applied to the root eleme
|
||||
|
||||
### 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
|
||||
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`.
|
||||
A parsed number could not be stored as without changing it to NaN or INF.
|
||||
|
||||
!!! failure "Example messages"
|
||||
!!! failure "Example message"
|
||||
|
||||
```
|
||||
number overflow parsing '10E1000'
|
||||
```
|
||||
```
|
||||
[json.exception.out_of_range.406] syntax error while parsing CBOR value: number overflow
|
||||
```
|
||||
|
||||
### json.exception.out_of_range.407
|
||||
|
||||
@@ -1047,13 +1070,24 @@ MessagePack's ext type and BSON's binary subtype are each stored in a single byt
|
||||
|
||||
[`basic_json_document::parse()`](../api/basic_json_document/parse.md) and the other parsing functions of
|
||||
[`basic_json_document`](../api/basic_json_document/index.md) index a value's position in the source text in 32 bits,
|
||||
so they do not support an input of 4 GiB or more.
|
||||
so they do not support an input of 4 GiB or more. The same 32-bit limit applies to an **editable** document's own
|
||||
storage: [`set`](../api/basic_json_document/set.md) and [`push_back`](../api/basic_json_document/push_back.md) throw
|
||||
this exception once the strings and number tokens written by edits reach 4 GiB in total, or once more than
|
||||
4294967295 arrays/objects have had an element set or appended to them. The same limit applies to an
|
||||
[image](../features/json_view.md#images): [`save()`](../api/basic_json_document/save.md) throws it if the node
|
||||
count, the text, or the decoded strings it would write would individually reach 4 GiB.
|
||||
|
||||
!!! failure "Example message"
|
||||
!!! failure "Example messages"
|
||||
|
||||
```
|
||||
[json.exception.out_of_range.416] input of 4 GiB or more is not supported by json_document
|
||||
```
|
||||
```
|
||||
[json.exception.out_of_range.416] edits of 4 GiB or more are not supported by json_document
|
||||
```
|
||||
```
|
||||
[json.exception.out_of_range.416] images of 4 GiB or more are not supported by json_document
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
|
||||
@@ -132,12 +132,6 @@ Build the unit tests when [`BUILD_TESTING`](https://cmake.org/cmake/help/latest/
|
||||
|
||||
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`
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
- 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
|
||||
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_msgpack`, `from_ubjson`, and `from_bson` is deprecated since 3.8.0, and to
|
||||
[`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, 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.
|
||||
`from_msgpack`, `from_ubjson`, and `from_bson` is deprecated since 3.8.0; call `from_cbor(ptr, ptr+len)` instead of
|
||||
`from_cbor(ptr, len)`.
|
||||
|
||||
=== "Deprecated"
|
||||
|
||||
|
||||
+10
-2
@@ -237,14 +237,20 @@ nav:
|
||||
- 'Overview': api/basic_json_document/index.md
|
||||
- '(Constructor)': api/basic_json_document/basic_json_document.md
|
||||
- 'accept': api/basic_json_document/accept.md
|
||||
- 'erase': api/basic_json_document/erase.md
|
||||
- 'insert': api/basic_json_document/insert.md
|
||||
- 'is_discarded': api/basic_json_document/is_discarded.md
|
||||
- 'load': api/basic_json_document/load.md
|
||||
- 'memory_usage': api/basic_json_document/memory_usage.md
|
||||
- 'node_count': api/basic_json_document/node_count.md
|
||||
- 'owns_source': api/basic_json_document/owns_source.md
|
||||
- 'parse': api/basic_json_document/parse.md
|
||||
- 'parse_copy': api/basic_json_document/parse_copy.md
|
||||
- 'push_back': api/basic_json_document/push_back.md
|
||||
- 'read': api/basic_json_document/read.md
|
||||
- 'root': api/basic_json_document/root.md
|
||||
- 'save': api/basic_json_document/save.md
|
||||
- 'set': api/basic_json_document/set.md
|
||||
- 'shrink_to_fit': api/basic_json_document/shrink_to_fit.md
|
||||
- 'source': api/basic_json_document/source.md
|
||||
- basic_json_view:
|
||||
@@ -307,6 +313,8 @@ nav:
|
||||
- 'to_json': api/adl_serializer/to_json.md
|
||||
- 'json': api/json.md
|
||||
- 'json_document': api/json_document.md
|
||||
- 'json_editable_document': api/json_editable_document.md
|
||||
- 'json_editable_view': api/json_editable_view.md
|
||||
- json_pointer:
|
||||
- 'Overview': api/json_pointer/index.md
|
||||
- '(Constructor)': api/json_pointer/json_pointer.md
|
||||
@@ -348,6 +356,8 @@ nav:
|
||||
- 'operator""_json_pointer': api/operator_literal_json_pointer.md
|
||||
- 'ordered_json': api/ordered_json.md
|
||||
- 'ordered_json_document': api/ordered_json_document.md
|
||||
- 'ordered_json_editable_document': api/ordered_json_editable_document.md
|
||||
- 'ordered_json_editable_view': api/ordered_json_editable_view.md
|
||||
- 'ordered_json_view': api/ordered_json_view.md
|
||||
- 'ordered_map': api/ordered_map.md
|
||||
- macros:
|
||||
@@ -356,7 +366,6 @@ nav:
|
||||
- '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_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_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md
|
||||
- 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md
|
||||
@@ -378,7 +387,6 @@ nav:
|
||||
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.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_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_VIEW_NO_SIMD': api/macros/json_view_no_simd.md
|
||||
- 'JSON_VIEW_USE_SSSE3': api/macros/json_view_use_ssse3.md
|
||||
|
||||
@@ -50,10 +50,6 @@
|
||||
#define JSON_STRICT_BINARY_UTF8 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||
#else
|
||||
@@ -96,20 +92,14 @@
|
||||
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
|
||||
#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
|
||||
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||
#endif
|
||||
|
||||
// Construct the namespace ABI tags component
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h) json_abi ## a ## b ## c ## d ## e ## f ## g ## h
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(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) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g)
|
||||
|
||||
#define NLOHMANN_JSON_ABI_TAGS \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||
@@ -119,8 +109,7 @@
|
||||
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
|
||||
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
|
||||
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS)
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8)
|
||||
|
||||
// Construct the namespace version component
|
||||
#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;
|
||||
value_type 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
|
||||
|
||||
@@ -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,
|
||||
typename = enable_if_t < !std::is_constructible <
|
||||
typename BasicJsonType::string_t, Key >::value >>
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -572,11 +543,6 @@ template < typename BasicJsonType, typename Key, typename Value, typename Hash,
|
||||
typename BasicJsonType::string_t, Key >::value >>
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include <valarray> // valarray
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/detail/exceptions.hpp>
|
||||
#include <nlohmann/detail/iterators/iteration_proxy.hpp>
|
||||
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||
#include <nlohmann/detail/meta/std_fs.hpp>
|
||||
@@ -287,20 +286,9 @@ struct external_constructor<value_t::object>
|
||||
/////////////
|
||||
|
||||
#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,
|
||||
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())
|
||||
{
|
||||
@@ -376,7 +364,7 @@ inline void to_json(BasicJsonType& j, EnumType e) noexcept
|
||||
{
|
||||
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;
|
||||
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
|
||||
|
||||
@@ -396,9 +384,6 @@ template < typename BasicJsonType, typename CompatibleArrayType,
|
||||
!is_basic_json<CompatibleArrayType>::value
|
||||
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
||||
&& !is_compatible_range_view<CompatibleArrayType>::value
|
||||
#endif
|
||||
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||
&& !is_enum_keyed_map<CompatibleArrayType>::value
|
||||
#endif
|
||||
,
|
||||
int > = 0 >
|
||||
@@ -453,33 +438,6 @@ inline void to_json(BasicJsonType& j, const CompatibleObjectType& 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>
|
||||
inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -221,7 +221,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
// 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
|
||||
/// 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
|
||||
@@ -2070,39 +2070,6 @@ scan_number_done:
|
||||
// read the next character and ignore 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
|
||||
while (ignore_comments && current == '/')
|
||||
{
|
||||
@@ -2182,6 +2149,7 @@ scan_number_done:
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/// input adapter
|
||||
InputAdapterType ia;
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ class parser
|
||||
}
|
||||
|
||||
// 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(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -423,7 +423,7 @@ class parser
|
||||
{
|
||||
// comma -> next value
|
||||
// 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
|
||||
get_token();
|
||||
@@ -463,7 +463,7 @@ class parser
|
||||
|
||||
// comma -> next value
|
||||
// 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();
|
||||
|
||||
@@ -484,7 +484,7 @@ class parser
|
||||
}
|
||||
|
||||
// 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(),
|
||||
m_lexer.get_token_string(),
|
||||
@@ -528,13 +528,6 @@ class parser
|
||||
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 error_msg = "syntax error ";
|
||||
|
||||
@@ -88,13 +88,9 @@ class json_pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_string_t/
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string())
|
||||
operator string_t() const
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return to_string();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
/// @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/
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer))
|
||||
bool operator==(const string_t& rhs) const
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return *this == json_pointer(rhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief 3-way compares two JSON pointers
|
||||
template<typename RefStringTypeRhs>
|
||||
@@ -1117,26 +1109,18 @@ template<typename RefStringTypeLhs,
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
|
||||
inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
||||
const StringType& rhs)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return lhs == json_pointer<RefStringTypeLhs>(rhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename RefStringTypeRhs,
|
||||
typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
|
||||
inline bool operator==(const StringType& lhs,
|
||||
const json_pointer<RefStringTypeRhs>& rhs)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return json_pointer<RefStringTypeRhs>(lhs) == rhs;
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
||||
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))
|
||||
inline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
|
||||
const StringType& rhs)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename RefStringTypeRhs,
|
||||
typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))
|
||||
inline bool operator!=(const StringType& lhs,
|
||||
const json_pointer<RefStringTypeRhs>& rhs)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
|
||||
inline bool operator<(const json_pointer<RefStringTypeLhs>& lhs,
|
||||
|
||||
@@ -888,10 +888,6 @@
|
||||
#define JSON_DISABLE_ENUM_SERIALIZATION 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
#define JSON_DELETE_DEPRECATED_FUNCTIONS 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
|
||||
#endif
|
||||
@@ -45,8 +45,6 @@
|
||||
#undef JSON_PRECISE_STREAM_POSITION
|
||||
#undef JSON_STRICT_NUL_HANDLING
|
||||
#undef JSON_STRICT_BINARY_UTF8
|
||||
#undef JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||
#endif
|
||||
|
||||
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
||||
@@ -438,30 +438,6 @@ template<typename BasicJsonType, typename CompatibleObjectType>
|
||||
struct is_compatible_object_type
|
||||
: 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,
|
||||
typename = void>
|
||||
struct is_constructible_object_type_impl : std::false_type {};
|
||||
@@ -906,21 +882,6 @@ T conditional_static_cast(U 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>
|
||||
using all_integral = conjunction<std::is_integral<Types>...>;
|
||||
|
||||
|
||||
@@ -127,7 +127,6 @@ class binary_writer
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@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)
|
||||
{
|
||||
@@ -159,7 +158,6 @@ class binary_writer
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_cbor(const BasicJsonType& j)
|
||||
{
|
||||
@@ -324,7 +322,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, "CBOR");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,7 +382,6 @@ class binary_writer
|
||||
|
||||
/*!
|
||||
@param[in] j JSON value to serialize
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_msgpack(const BasicJsonType& j)
|
||||
{
|
||||
@@ -658,7 +655,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, "MessagePack");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -671,7 +668,6 @@ class binary_writer
|
||||
@param[in] bjdata_version which BJData version to use, default is draft2
|
||||
@throw type_error.316 if a string value or an object key is not valid
|
||||
UTF-8
|
||||
@throw type_error.321 if @a j or a value nested in it is discarded
|
||||
*/
|
||||
void write_ubjson(const BasicJsonType& j, const bool use_count,
|
||||
const bool use_type, const bool add_prefix = true,
|
||||
@@ -905,7 +901,7 @@ class binary_writer
|
||||
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -925,15 +921,6 @@ class binary_writer
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief throws because @a j is discarded and cannot be serialized
|
||||
@throw type_error.321 always
|
||||
*/
|
||||
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
|
||||
{
|
||||
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
|
||||
}
|
||||
|
||||
//////////
|
||||
// BSON //
|
||||
//////////
|
||||
@@ -1185,7 +1172,6 @@ class binary_writer
|
||||
into a byte, before anything is written
|
||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||
anything is written
|
||||
@throw type_error.321 if @a j is discarded
|
||||
*/
|
||||
std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||
{
|
||||
@@ -1212,12 +1198,10 @@ class binary_writer
|
||||
case value_t::null:
|
||||
return 0ul;
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||
return 0ul;
|
||||
@@ -1254,12 +1238,10 @@ class binary_writer
|
||||
case value_t::null:
|
||||
return write_bson_null(name);
|
||||
|
||||
case value_t::discarded:
|
||||
throw_on_discarded(j, "BSON");
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||
return;
|
||||
@@ -1326,8 +1308,6 @@ class binary_writer
|
||||
byte, before anything is written
|
||||
@throw type_error.316 if a string value or a key is not valid UTF-8,
|
||||
before anything is written
|
||||
@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)
|
||||
{
|
||||
|
||||
@@ -164,12 +164,14 @@ class builder
|
||||
if (p[1] == '/')
|
||||
{
|
||||
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))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
if (NulIsEnd && p != e && *p == 0)
|
||||
{
|
||||
++p; // as in parse(), a null byte ends the comment like a line break
|
||||
}
|
||||
return p;
|
||||
}
|
||||
if (p[1] == '*')
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <cstring> // memcpy
|
||||
#include <functional> // less
|
||||
#include <map> // map
|
||||
#include <memory> // unique_ptr
|
||||
#include <new> // operator new, placement new
|
||||
#include <string> // string
|
||||
#include <vector> // vector
|
||||
@@ -38,7 +41,9 @@ struct document_data
|
||||
node* inline_tape = nullptr; ///< node array allocated together with this header
|
||||
std::size_t inline_cap = 0;
|
||||
std::string arena{}; ///< decoded strings that contained escapes // NOLINT(readability-redundant-member-init)
|
||||
std::size_t arena_size = 0; ///< bytes of decoded strings at base[1] (the arena, or those of a loaded image)
|
||||
std::string owned{}; ///< owned copy of the input, if any // NOLINT(readability-redundant-member-init)
|
||||
std::vector<std::uint8_t> owned_image{}; ///< a loaded image the document owns (the text and the decoded strings point into it) // NOLINT(readability-redundant-member-init)
|
||||
|
||||
// hash indexes of large objects (see object_index.hpp)
|
||||
static constexpr std::uint32_t index_min_members = 128;
|
||||
@@ -50,9 +55,30 @@ struct document_data
|
||||
std::vector<object_index> indexes{}; // NOLINT(readability-redundant-member-init)
|
||||
std::vector<std::uint32_t> index_slots{}; // NOLINT(readability-redundant-member-init)
|
||||
std::vector<std::uint32_t> large_objects{}; ///< positions of the objects to index (noted while parsing) // NOLINT(readability-redundant-member-init)
|
||||
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena (indexed by flags & node_flags::storage)
|
||||
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena, edit arena (indexed by flags & node_flags::storage)
|
||||
bool discarded = true;
|
||||
|
||||
/// The storage of edits (editable documents only; see edit_storage.hpp).
|
||||
/// Edits never move or resize the parsed index, so views stay valid: an
|
||||
/// array/object whose elements change gets node_flags::moved, and its
|
||||
/// elements then live in a separate sequence (a header node, then the
|
||||
/// entries), whose entries link to the values.
|
||||
struct edit_state
|
||||
{
|
||||
std::vector<node*> moved{}; ///< element sequences of moved arrays/objects (header node first) // NOLINT(readability-redundant-member-init)
|
||||
std::vector<std::size_t> moved_cap{}; ///< capacity in nodes of a growable block; 0: a fixed sequence (a new value) // NOLINT(readability-redundant-member-init)
|
||||
std::vector<std::unique_ptr<node[]>> chunks{}; ///< storage of new values and blocks; never moved // NOLINT(readability-redundant-member-init,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
std::map<const node*, node*, std::less<const node*>> regions{}; ///< new arrays/objects: root -> container that uses it as its element sequence (nullptr: linked from a block) // NOLINT(readability-redundant-member-init)
|
||||
node* chunk_cur = nullptr;
|
||||
node* chunk_end = nullptr;
|
||||
std::size_t chunk_next = 64;
|
||||
std::vector<std::unique_ptr<char[]>> texts{}; ///< edit arena, the current buffer last; earlier ones stay alive for string views // NOLINT(readability-redundant-member-init,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
std::size_t text_used = 0;
|
||||
std::size_t text_cap = 0;
|
||||
std::size_t bytes = 0; ///< memory held by edits
|
||||
};
|
||||
std::unique_ptr<edit_state> edits{}; ///< created by the first edit // NOLINT(readability-redundant-member-init)
|
||||
|
||||
/// one allocation for the header and room for `nodes` nodes; large
|
||||
/// documents get a separate node array instead (so it can be trimmed)
|
||||
static document_data* create(std::size_t nodes)
|
||||
@@ -136,6 +162,78 @@ struct document_data
|
||||
{
|
||||
return n + n->next;
|
||||
}
|
||||
|
||||
/// (editable documents) first element or key, also of a moved container
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* first_child_edited(const node* n) const noexcept
|
||||
{
|
||||
return NLOHMANN_VIEW_LIKELY((n->flags & node_flags::moved) == 0) ? n + 1 : edits->moved[n->off] + 1;
|
||||
}
|
||||
|
||||
/// (editable documents) end of the elements, also of a moved container
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* child_end_edited(const node* n) const noexcept
|
||||
{
|
||||
if (NLOHMANN_VIEW_LIKELY((n->flags & node_flags::moved) == 0))
|
||||
{
|
||||
return n + n->next;
|
||||
}
|
||||
const node* const h = edits->moved[n->off];
|
||||
return h + h->next;
|
||||
}
|
||||
|
||||
/// (editable documents) the value at an element position: entries of
|
||||
/// moved sequences are links. The link case is out of line, so that this
|
||||
/// compiles to a predicted branch rather than a select that delays the
|
||||
/// following loads.
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* deref(const node* n) noexcept
|
||||
{
|
||||
return NLOHMANN_VIEW_LIKELY(n->kind != kind_link) ? n : follow_link(n);
|
||||
}
|
||||
|
||||
static NLOHMANN_VIEW_NOINLINE const node* follow_link(const node* n) noexcept
|
||||
{
|
||||
return link_target(*n);
|
||||
}
|
||||
};
|
||||
|
||||
/// How the index is walked: views of read-only documents follow the node
|
||||
/// array alone and compile without any of the edit handling; views of
|
||||
/// editable documents also follow moved element sequences and links.
|
||||
template<bool Editable>
|
||||
struct navigation
|
||||
{
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* first(const document_data& /*d*/, const node* n) noexcept
|
||||
{
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* end(const document_data& /*d*/, const node* n) noexcept
|
||||
{
|
||||
return n + n->next;
|
||||
}
|
||||
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* value(const node* n) noexcept
|
||||
{
|
||||
return n;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct navigation<true>
|
||||
{
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* first(const document_data& d, const node* n) noexcept
|
||||
{
|
||||
return d.first_child_edited(n);
|
||||
}
|
||||
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* end(const document_data& d, const node* n) noexcept
|
||||
{
|
||||
return d.child_end_edited(n);
|
||||
}
|
||||
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* value(const node* n) noexcept
|
||||
{
|
||||
return document_data::deref(n);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace view
|
||||
|
||||
@@ -0,0 +1,767 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array> // array
|
||||
#include <cmath> // isinf, isnan
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint8_t, uint32_t, uint64_t
|
||||
#include <cstring> // memcmp, memmove
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string, to_string
|
||||
#include <type_traits> // decay, enable_if, integral_constant, is_arithmetic, is_convertible, is_floating_point, is_same, is_signed
|
||||
#include <utility> // forward
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/edit_storage.hpp>
|
||||
#include <nlohmann/detail/view/errors.hpp>
|
||||
#include <nlohmann/detail/view/lookup.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
template<typename BasicJsonType, bool Editable>
|
||||
class basic_json_view;
|
||||
|
||||
namespace detail
|
||||
{
|
||||
namespace view
|
||||
{
|
||||
|
||||
/// the index of the first true condition (the number of conditions if none is)
|
||||
template<bool... Conditions>
|
||||
struct first_true : std::integral_constant<int, 0> {};
|
||||
|
||||
template<bool... Conditions>
|
||||
struct first_true<false, Conditions...> : std::integral_constant < int, 1 + first_true<Conditions...>::value > {};
|
||||
|
||||
/// Checks a string the way basic_json's serializer does when it writes it
|
||||
/// (type_error.316 with the same message), so that an editable document
|
||||
/// only holds valid UTF-8: the error is at the first byte that no
|
||||
/// well-formed sequence can continue with (Unicode, Table 3-7).
|
||||
inline void check_utf8(const char* s, std::size_t n)
|
||||
{
|
||||
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const auto hex = [](unsigned char c)
|
||||
{
|
||||
constexpr const char* digits = "0123456789ABCDEF";
|
||||
return std::string{digits[c >> 4u], digits[c & 0xFu]};
|
||||
};
|
||||
for (std::size_t i = 0; i < n;)
|
||||
{
|
||||
const unsigned char c = p[i];
|
||||
if (c < 0x80)
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
std::size_t len = 0;
|
||||
unsigned char lo = 0x80;
|
||||
unsigned char hi = 0xBF;
|
||||
if (c >= 0xC2 && c <= 0xDF)
|
||||
{
|
||||
len = 2;
|
||||
}
|
||||
else if (c >= 0xE0 && c <= 0xEF)
|
||||
{
|
||||
len = 3;
|
||||
lo = c == 0xE0 ? 0xA0 : 0x80;
|
||||
hi = c == 0xED ? 0x9F : 0xBF;
|
||||
}
|
||||
else if (c >= 0xF0 && c <= 0xF4)
|
||||
{
|
||||
len = 4;
|
||||
lo = c == 0xF0 ? 0x90 : 0x80;
|
||||
hi = c == 0xF4 ? 0x8F : 0xBF;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex(c)));
|
||||
}
|
||||
for (std::size_t k = 1; k < len; ++k)
|
||||
{
|
||||
if (i + k == n)
|
||||
{
|
||||
throw_type_error(316, concat("incomplete UTF-8 string; last byte: 0x", hex(p[n - 1])));
|
||||
}
|
||||
const unsigned char b = p[i + k];
|
||||
if (b < (k == 1 ? lo : 0x80) || b > (k == 1 ? hi : 0xBF))
|
||||
{
|
||||
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex(b)));
|
||||
}
|
||||
}
|
||||
i += len;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the edits of an editable basic_json_document
|
||||
|
||||
Values are accepted as views (of any document), BasicJsonType values, and
|
||||
everything BasicJsonType can be constructed from. The source text is never
|
||||
written: new values go to storage owned by the document (see
|
||||
edit_storage.hpp).
|
||||
*/
|
||||
template<typename BasicJsonType, typename View>
|
||||
class editor
|
||||
{
|
||||
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 string_t = typename BasicJsonType::string_t;
|
||||
using string_view_t = typename View::string_view_t;
|
||||
using nav = navigation<true>;
|
||||
|
||||
public:
|
||||
explicit editor(document_data& d) noexcept
|
||||
: m_doc(d)
|
||||
{}
|
||||
|
||||
/// replace a value; returns its view
|
||||
template<typename V>
|
||||
View set(const View& target, V&& value)
|
||||
{
|
||||
node* const slot = own(target);
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
assign(slot, e, nullptr, false);
|
||||
return View(&m_doc, slot);
|
||||
}
|
||||
|
||||
/// set a member (appended if missing; a null value becomes an object);
|
||||
/// returns a view of the member value
|
||||
template<typename V>
|
||||
View set(const View& object, string_view_t key, V&& value)
|
||||
{
|
||||
node* const o = own(object);
|
||||
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
|
||||
{
|
||||
throw_type_error(305, "cannot use operator[] with a string argument with ", object.type_name());
|
||||
}
|
||||
check_utf8(key.data(), key.size());
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
if (o->kind == static_cast<std::uint8_t>(value_t::null))
|
||||
{
|
||||
become_empty(o, value_t::object);
|
||||
}
|
||||
// an existing member: assign it (and drop later duplicates, so that
|
||||
// lookups, iteration, and materialize() agree)
|
||||
node* slot = nullptr;
|
||||
bool duplicates = false;
|
||||
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
||||
{
|
||||
if (key_equals(*k, key))
|
||||
{
|
||||
if (slot != nullptr)
|
||||
{
|
||||
duplicates = true;
|
||||
break;
|
||||
}
|
||||
slot = const_cast<node*>(nav::value(k + 1)); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
||||
}
|
||||
}
|
||||
if (slot != nullptr)
|
||||
{
|
||||
if (duplicates)
|
||||
{
|
||||
erase_members(o, key, true);
|
||||
}
|
||||
assign(slot, e, o, true);
|
||||
return View(&m_doc, slot);
|
||||
}
|
||||
const node k = string_node(key.data(), key.size());
|
||||
slot = new_slot(e);
|
||||
node* const h = block_of(m_doc, o, 2);
|
||||
h[h->next] = k;
|
||||
make_link(h[h->next + 1], slot);
|
||||
h->next += 2;
|
||||
++h->len;
|
||||
++o->len;
|
||||
return View(&m_doc, slot);
|
||||
}
|
||||
|
||||
/// assign an existing array element; returns a view of it
|
||||
template<typename V>
|
||||
View set(const View& array, std::size_t idx, V&& value)
|
||||
{
|
||||
node* const a = own(array);
|
||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||
{
|
||||
throw_type_error(305, "cannot use operator[] with a numeric argument with ", array.type_name());
|
||||
}
|
||||
check_index(idx, a->len);
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
node* const slot = const_cast<node*>(nav::value(element_at<true>(m_doc, a, idx))); // NOLINT(cppcoreguidelines-pro-type-const-cast)
|
||||
assign(slot, e, a, true);
|
||||
return View(&m_doc, slot);
|
||||
}
|
||||
|
||||
/// append to an array (a null value becomes an array); returns a view of
|
||||
/// the new element
|
||||
template<typename V>
|
||||
View push_back(const View& array, V&& value)
|
||||
{
|
||||
node* const a = own(array);
|
||||
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
|
||||
{
|
||||
throw_type_error(308, "cannot use push_back() with ", array.type_name());
|
||||
}
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
if (a->kind == static_cast<std::uint8_t>(value_t::null))
|
||||
{
|
||||
become_empty(a, value_t::array);
|
||||
}
|
||||
node* const slot = new_slot(e);
|
||||
node* const h = block_of(m_doc, a, 1);
|
||||
make_link(h[h->next], slot);
|
||||
++h->next;
|
||||
++h->len;
|
||||
++a->len;
|
||||
return View(&m_doc, slot);
|
||||
}
|
||||
|
||||
/// insert into an array before position idx (idx <= size()); returns a
|
||||
/// view of the new element
|
||||
template<typename V>
|
||||
View insert(const View& array, std::size_t idx, V&& value)
|
||||
{
|
||||
node* const a = own(array);
|
||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||
{
|
||||
throw_type_error(309, "cannot use insert() with ", array.type_name());
|
||||
}
|
||||
check_index(idx, a->len + 1);
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
node* const slot = new_slot(e);
|
||||
node* const h = block_of(m_doc, a, 1);
|
||||
std::memmove(h + 2 + idx, h + 1 + idx, (h->next - 1 - idx) * sizeof(node));
|
||||
make_link(h[1 + idx], slot);
|
||||
++h->next;
|
||||
++h->len;
|
||||
++a->len;
|
||||
return View(&m_doc, slot);
|
||||
}
|
||||
|
||||
/// remove all members with this key; returns their number
|
||||
std::size_t erase(const View& object, string_view_t key)
|
||||
{
|
||||
node* const o = own(object);
|
||||
if (o->kind != static_cast<std::uint8_t>(value_t::object))
|
||||
{
|
||||
throw_type_error(307, "cannot use erase() with ", object.type_name());
|
||||
}
|
||||
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
||||
{
|
||||
if (key_equals(*k, key))
|
||||
{
|
||||
return erase_members(o, key, false);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// remove an array element
|
||||
void erase(const View& array, std::size_t idx)
|
||||
{
|
||||
node* const a = own(array);
|
||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||
{
|
||||
throw_type_error(307, "cannot use erase() with ", array.type_name());
|
||||
}
|
||||
check_index(idx, a->len);
|
||||
node* const h = block_of(m_doc, a, 0);
|
||||
std::memmove(h + 1 + idx, h + 2 + idx, (h->next - 2 - idx) * sizeof(node));
|
||||
--h->next;
|
||||
--h->len;
|
||||
--a->len;
|
||||
}
|
||||
|
||||
private:
|
||||
/// an encoded value: a scalar node, or the root of a new array/object
|
||||
struct encoded
|
||||
{
|
||||
node scalar{};
|
||||
node* region = nullptr;
|
||||
};
|
||||
|
||||
/// the node of a view of this document
|
||||
node* own(const View& v)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
|
||||
{
|
||||
throw_invalid_iterator(202, "view does not belong to this document");
|
||||
}
|
||||
edit_state_of(m_doc);
|
||||
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
||||
}
|
||||
|
||||
static void check_index(std::size_t idx, std::size_t limit)
|
||||
{
|
||||
if (idx >= limit)
|
||||
{
|
||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
}
|
||||
|
||||
bool key_equals(const node& k, string_view_t key) const noexcept
|
||||
{
|
||||
return k.len == key.size() && (key.size() == 0 || std::memcmp(m_doc.str(k), key.data(), key.size()) == 0);
|
||||
}
|
||||
|
||||
/// remove the members with this key (all, or all but the first) from an object
|
||||
std::size_t erase_members(node* o, string_view_t key, bool keep_first)
|
||||
{
|
||||
node* const h = block_of(m_doc, o, 0);
|
||||
node* w = h + 1;
|
||||
std::size_t erased = 0;
|
||||
bool kept = false;
|
||||
for (node* r = h + 1, *end = h + h->next; r != end; r += 2)
|
||||
{
|
||||
const bool match = key_equals(*r, key);
|
||||
if (match && (kept || !keep_first))
|
||||
{
|
||||
++erased;
|
||||
continue;
|
||||
}
|
||||
kept = kept || match;
|
||||
if (w != r)
|
||||
{
|
||||
w[0] = r[0];
|
||||
w[1] = r[1];
|
||||
}
|
||||
w += 2;
|
||||
}
|
||||
h->next = static_cast<std::uint32_t>(w - h);
|
||||
h->len -= static_cast<std::uint32_t>(erased);
|
||||
o->len -= static_cast<std::uint32_t>(erased);
|
||||
return erased;
|
||||
}
|
||||
|
||||
/// turn a null into an empty array/object in place
|
||||
static void become_empty(node* n, value_t k) noexcept
|
||||
{
|
||||
*n = node{};
|
||||
n->kind = static_cast<std::uint8_t>(k);
|
||||
n->flags = node_flags::is_new;
|
||||
n->next = 1;
|
||||
}
|
||||
|
||||
/// Replace the value at slot; `parent` is the container whose elements
|
||||
/// include slot (if known).
|
||||
void assign(node* slot, const encoded& e, node* parent, bool parent_known)
|
||||
{
|
||||
if (e.region == nullptr)
|
||||
{
|
||||
if (is_container(*slot) && slot->next > 1 && slot != m_doc.tape)
|
||||
{
|
||||
// The slot spans its old elements in the enclosing sequence, but
|
||||
// a scalar is one node: the enclosing container first switches to
|
||||
// links (then the extent of the slot no longer matters).
|
||||
node* const p = parent_known ? parent : find_parent(m_doc, slot);
|
||||
if (p != nullptr && ((p->flags & node_flags::moved) == 0 || moved_capacity(m_doc, p) == 0))
|
||||
{
|
||||
block_of(m_doc, p, 0);
|
||||
}
|
||||
}
|
||||
*slot = e.scalar;
|
||||
return;
|
||||
}
|
||||
// an array/object: the slot keeps its extent (so that the enclosing
|
||||
// sequence still steps over it), and the elements come from the new
|
||||
// sequence
|
||||
const node* const r = e.region;
|
||||
const std::uint32_t extent = is_container(*slot) ? slot->next : 1;
|
||||
const bool was_moved = (slot->flags & node_flags::moved) != 0;
|
||||
slot->kind = r->kind;
|
||||
slot->extra = 0;
|
||||
slot->len = r->len;
|
||||
slot->next = extent;
|
||||
slot->flags = was_moved ? static_cast<std::uint8_t>(node_flags::moved | node_flags::is_new) : std::uint8_t{0};
|
||||
set_moved(m_doc, slot, e.region, 0);
|
||||
edit_state_of(m_doc).regions[e.region] = slot;
|
||||
}
|
||||
|
||||
/// a node for a new element (links point to it; it never moves)
|
||||
node* new_slot(const encoded& e)
|
||||
{
|
||||
if (e.region != nullptr)
|
||||
{
|
||||
return e.region;
|
||||
}
|
||||
node* const s = alloc_nodes(m_doc, 1);
|
||||
*s = e.scalar;
|
||||
return s;
|
||||
}
|
||||
|
||||
//////////////
|
||||
// encoding //
|
||||
//////////////
|
||||
|
||||
template<int N>
|
||||
using encode_tag = std::integral_constant<int, N>;
|
||||
|
||||
template<typename T>
|
||||
struct is_view : std::false_type {};
|
||||
|
||||
template<typename J, bool E>
|
||||
struct is_view<basic_json_view<J, E>> : std::true_type {};
|
||||
|
||||
template<typename V>
|
||||
encoded encode(V&& v)
|
||||
{
|
||||
using D = typename std::decay<V>::type;
|
||||
return encode_impl(std::forward<V>(v), encode_tag<first_true<is_view<D>::value,
|
||||
std::is_same<D, BasicJsonType>::value,
|
||||
std::is_same<D, std::nullptr_t>::value,
|
||||
std::is_same<D, bool>::value,
|
||||
std::is_arithmetic<D>::value,
|
||||
std::is_convertible<const D&, string_view_t>::value>::value> {});
|
||||
}
|
||||
|
||||
/// a view of any document (copied; nothing is shared with it)
|
||||
template<typename J, bool E>
|
||||
encoded encode_impl(const basic_json_view<J, E>& v, encode_tag<0> /*view*/)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
|
||||
{
|
||||
throw_type_error(302, "type must be a value, but is ", "discarded");
|
||||
}
|
||||
encoded r;
|
||||
if (!is_container(*v.m_node))
|
||||
{
|
||||
r.scalar = copy_scalar(*v.m_doc, *v.m_node);
|
||||
return r;
|
||||
}
|
||||
r.region = alloc_nodes(m_doc, count_nodes<E>(*v.m_doc, v.m_node));
|
||||
fill_nodes<E>(*v.m_doc, v.m_node, r.region);
|
||||
edit_state_of(m_doc).regions.emplace(r.region, nullptr);
|
||||
return r;
|
||||
}
|
||||
|
||||
encoded encode_impl(const BasicJsonType& j, encode_tag<1> /*json*/)
|
||||
{
|
||||
encoded r;
|
||||
if (!j.is_structured())
|
||||
{
|
||||
r.scalar = json_scalar(j);
|
||||
return r;
|
||||
}
|
||||
r.region = alloc_nodes(m_doc, count_nodes(j));
|
||||
fill_nodes(j, r.region);
|
||||
edit_state_of(m_doc).regions.emplace(r.region, nullptr);
|
||||
return r;
|
||||
}
|
||||
|
||||
encoded encode_impl(std::nullptr_t /*unused*/, encode_tag<2> /*null*/)
|
||||
{
|
||||
encoded r;
|
||||
r.scalar = plain_node(value_t::null);
|
||||
return r;
|
||||
}
|
||||
|
||||
encoded encode_impl(bool b, encode_tag<3> /*boolean*/)
|
||||
{
|
||||
encoded r;
|
||||
r.scalar = plain_node(value_t::boolean);
|
||||
r.scalar.flags = static_cast<std::uint8_t>(r.scalar.flags | (b ? node_flags::is_true : 0));
|
||||
return r;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
encoded encode_impl(T x, encode_tag<4> /*number*/)
|
||||
{
|
||||
encoded r;
|
||||
r.scalar = number_node(x, std::integral_constant<int, first_true<std::is_floating_point<T>::value, std::is_signed<T>::value>::value> {});
|
||||
return r;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
encoded encode_impl(const T& s, encode_tag<5> /*string*/)
|
||||
{
|
||||
const string_view_t sv(s);
|
||||
check_utf8(sv.data(), sv.size());
|
||||
encoded r;
|
||||
r.scalar = string_node(sv.data(), sv.size());
|
||||
return r;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
encoded encode_impl(T&& x, encode_tag<6> /*other*/)
|
||||
{
|
||||
return encode_impl(BasicJsonType(std::forward<T>(x)), encode_tag<1> {});
|
||||
}
|
||||
|
||||
static node plain_node(value_t k) noexcept
|
||||
{
|
||||
node n{};
|
||||
n.kind = static_cast<std::uint8_t>(k);
|
||||
n.flags = node_flags::is_new;
|
||||
return n;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
node number_node(T x, std::integral_constant<int, 0> /*floating-point*/)
|
||||
{
|
||||
return float_node(static_cast<number_float_t>(x));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
node number_node(T x, std::integral_constant<int, 1> /*signed*/)
|
||||
{
|
||||
return integer_node(static_cast<std::uint64_t>(static_cast<std::int64_t>(x)), value_t::number_integer);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
node number_node(T x, std::integral_constant<int, 2> /*unsigned*/)
|
||||
{
|
||||
return integer_node(static_cast<std::uint64_t>(x), value_t::number_unsigned);
|
||||
}
|
||||
|
||||
/// an integer with its canonical token in the edit arena
|
||||
node integer_node(std::uint64_t bits, value_t k)
|
||||
{
|
||||
const bool negative = k == value_t::number_integer && static_cast<std::int64_t>(bits) < 0;
|
||||
std::uint64_t magnitude = negative ? 0 - bits : bits;
|
||||
std::array<char, 24> buf{};
|
||||
char* p = buf.data() + buf.size();
|
||||
do
|
||||
{
|
||||
*--p = static_cast<char>('0' + (magnitude % 10));
|
||||
magnitude /= 10;
|
||||
}
|
||||
while (magnitude != 0);
|
||||
if (negative)
|
||||
{
|
||||
*--p = '-';
|
||||
}
|
||||
const auto len = static_cast<std::size_t>(buf.data() + buf.size() - p);
|
||||
node n = plain_node(k);
|
||||
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
||||
n.off = append_text(m_doc, p, len);
|
||||
// number_length() adds one for the sign of number_integer nodes
|
||||
n.extra = static_cast<std::uint16_t>(k == value_t::number_integer ? len - 1 : len);
|
||||
set_integer_bits(n, bits);
|
||||
return n;
|
||||
}
|
||||
|
||||
/// a float with its shortest round-trip token (as basic_json::dump()
|
||||
/// writes it), or nan, inf, -inf, in the edit arena
|
||||
node float_node(number_float_t x)
|
||||
{
|
||||
string_t text;
|
||||
if (std::isnan(x))
|
||||
{
|
||||
text = "nan";
|
||||
}
|
||||
else if (std::isinf(x))
|
||||
{
|
||||
text = x > 0 ? "inf" : "-inf";
|
||||
}
|
||||
else
|
||||
{
|
||||
text = BasicJsonType(x).dump();
|
||||
}
|
||||
node n = plain_node(value_t::number_float);
|
||||
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
||||
n.extra = 0xFFFFu; // (the digit layout is not recorded)
|
||||
n.off = append_text(m_doc, text.data(), text.size());
|
||||
n.len = static_cast<std::uint32_t>(text.size());
|
||||
return n;
|
||||
}
|
||||
|
||||
/// a string (or key) in the edit arena
|
||||
node string_node(const char* s, std::size_t len)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
|
||||
{
|
||||
throw_out_of_range(416, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
|
||||
}
|
||||
node n = plain_node(value_t::string);
|
||||
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
||||
n.off = append_text(m_doc, s, len);
|
||||
n.len = static_cast<std::uint32_t>(len);
|
||||
return n;
|
||||
}
|
||||
|
||||
/// a scalar of a view (of any document) as a node of this document
|
||||
node copy_scalar(const document_data& from, const node& n)
|
||||
{
|
||||
if (&from == &m_doc)
|
||||
{
|
||||
return n; // the same storage
|
||||
}
|
||||
switch (static_cast<value_t>(n.kind))
|
||||
{
|
||||
case value_t::string:
|
||||
return string_node(from.str(n), n.len);
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
return integer_node(integer_bits(n), static_cast<value_t>(n.kind));
|
||||
case value_t::number_float:
|
||||
{
|
||||
node r = plain_node(value_t::number_float);
|
||||
r.flags = static_cast<std::uint8_t>(r.flags | node_flags::edited);
|
||||
r.off = append_text(m_doc, from.str(n), n.len);
|
||||
r.len = n.len;
|
||||
r.extra = n.extra;
|
||||
return r;
|
||||
}
|
||||
case value_t::boolean:
|
||||
{
|
||||
node r = plain_node(value_t::boolean);
|
||||
r.flags = static_cast<std::uint8_t>(r.flags | (n.flags & node_flags::is_true));
|
||||
return r;
|
||||
}
|
||||
case value_t::null:
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
return plain_node(value_t::null);
|
||||
}
|
||||
}
|
||||
|
||||
node json_scalar(const BasicJsonType& j)
|
||||
{
|
||||
switch (j.type())
|
||||
{
|
||||
case value_t::null:
|
||||
return plain_node(value_t::null);
|
||||
case value_t::boolean:
|
||||
{
|
||||
node r = plain_node(value_t::boolean);
|
||||
r.flags = static_cast<std::uint8_t>(r.flags | (j.template get<bool>() ? node_flags::is_true : 0));
|
||||
return r;
|
||||
}
|
||||
case value_t::number_integer:
|
||||
return integer_node(static_cast<std::uint64_t>(static_cast<std::int64_t>(j.template get<number_integer_t>())), value_t::number_integer);
|
||||
case value_t::number_unsigned:
|
||||
return integer_node(static_cast<std::uint64_t>(j.template get<number_unsigned_t>()), value_t::number_unsigned);
|
||||
case value_t::number_float:
|
||||
return float_node(j.template get<number_float_t>());
|
||||
case value_t::string:
|
||||
{
|
||||
const auto& s = j.template get_ref<const string_t&>();
|
||||
check_utf8(s.data(), s.size());
|
||||
return string_node(s.data(), s.size());
|
||||
}
|
||||
case value_t::binary:
|
||||
throw_type_error(319, "cannot store a binary value in a json_document", "");
|
||||
case value_t::discarded:
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
default:
|
||||
throw_type_error(302, "type must be a value, but is ", "discarded");
|
||||
}
|
||||
}
|
||||
|
||||
/// number of nodes of a subtree (containers, keys, scalars)
|
||||
template<bool E>
|
||||
static std::size_t count_nodes(const document_data& d, const node* n)
|
||||
{
|
||||
if (!is_container(*n))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||
std::size_t r = 1;
|
||||
for (const node* c = navigation<E>::first(d, n), *end = navigation<E>::end(d, n); c != end;)
|
||||
{
|
||||
const node* const v = object ? c + 1 : c;
|
||||
r += (object ? 1 : 0) + count_nodes<E>(d, navigation<E>::value(v));
|
||||
c = document_data::after(v);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/// copy a subtree (of any document) as a contiguous sequence; returns its end
|
||||
template<bool E>
|
||||
node* fill_nodes(const document_data& d, const node* n, node* out)
|
||||
{
|
||||
if (!is_container(*n))
|
||||
{
|
||||
*out = copy_scalar(d, *n);
|
||||
return out + 1;
|
||||
}
|
||||
node* const self = out++;
|
||||
*self = plain_node(static_cast<value_t>(n->kind));
|
||||
self->len = n->len;
|
||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||
for (const node* c = navigation<E>::first(d, n), *end = navigation<E>::end(d, n); c != end;)
|
||||
{
|
||||
if (object)
|
||||
{
|
||||
*out++ = copy_scalar(d, *c);
|
||||
++c;
|
||||
}
|
||||
out = fill_nodes<E>(d, navigation<E>::value(c), out);
|
||||
c = document_data::after(c);
|
||||
}
|
||||
self->next = static_cast<std::uint32_t>(out - self);
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::size_t count_nodes(const BasicJsonType& j)
|
||||
{
|
||||
std::size_t r = 1;
|
||||
if (j.is_object())
|
||||
{
|
||||
for (const auto& member : j.items())
|
||||
{
|
||||
r += 1 + count_nodes(member.value());
|
||||
}
|
||||
}
|
||||
else if (j.is_array())
|
||||
{
|
||||
for (const auto& e : j)
|
||||
{
|
||||
r += count_nodes(e);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
node* fill_nodes(const BasicJsonType& j, node* out)
|
||||
{
|
||||
if (!j.is_structured())
|
||||
{
|
||||
*out = json_scalar(j);
|
||||
return out + 1;
|
||||
}
|
||||
node* const self = out++;
|
||||
*self = plain_node(j.type());
|
||||
self->len = static_cast<std::uint32_t>(j.size());
|
||||
if (j.is_object())
|
||||
{
|
||||
for (const auto& member : j.items())
|
||||
{
|
||||
check_utf8(member.key().data(), member.key().size());
|
||||
*out++ = string_node(member.key().data(), member.key().size());
|
||||
out = fill_nodes(member.value(), out);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& e : j)
|
||||
{
|
||||
out = fill_nodes(e, out);
|
||||
}
|
||||
}
|
||||
self->next = static_cast<std::uint32_t>(out - self);
|
||||
return out;
|
||||
}
|
||||
|
||||
document_data& m_doc;
|
||||
};
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -0,0 +1,242 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // max, min
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <cstring> // memcpy
|
||||
#include <functional> // less
|
||||
#include <memory> // unique_ptr
|
||||
#include <utility> // move
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/errors.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
|
||||
// The storage of edits. Edits never move or resize the parsed index: every
|
||||
// value keeps its node, so views stay valid. New values and element sequences
|
||||
// live in chunks that never move; strings and number tokens written by edits
|
||||
// live in the edit arena. An array/object whose elements change gets
|
||||
// node_flags::moved: its elements then live in a separate sequence (a header
|
||||
// node, then the entries), whose entries link to the values (kind_link).
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
namespace view
|
||||
{
|
||||
|
||||
inline document_data::edit_state& edit_state_of(document_data& d)
|
||||
{
|
||||
if (!d.edits)
|
||||
{
|
||||
d.edits.reset(new document_data::edit_state()); // NOLINT(cppcoreguidelines-owning-memory): owned by the unique_ptr
|
||||
}
|
||||
return *d.edits;
|
||||
}
|
||||
|
||||
/// k consecutive nodes that never move (new values and blocks)
|
||||
inline node* alloc_nodes(document_data& d, std::size_t k)
|
||||
{
|
||||
document_data::edit_state& e = edit_state_of(d);
|
||||
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(e.chunk_end - e.chunk_cur) < k))
|
||||
{
|
||||
const std::size_t count = (std::max)(k, e.chunk_next);
|
||||
std::unique_ptr<node[]> fresh(new node[count]()); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
e.chunks.push_back(std::move(fresh));
|
||||
e.chunk_cur = e.chunks.back().get();
|
||||
e.chunk_end = e.chunk_cur + count;
|
||||
e.chunk_next = (std::min)(e.chunk_next * 2, std::size_t{65536});
|
||||
e.bytes += count * sizeof(node);
|
||||
}
|
||||
node* const r = e.chunk_cur;
|
||||
e.chunk_cur += k;
|
||||
return r;
|
||||
}
|
||||
|
||||
/// copy n bytes into the edit arena and return their offset; a new buffer
|
||||
/// leaves the old one alive, so that string views into it remain valid
|
||||
inline std::uint32_t append_text(document_data& d, const char* s, std::size_t n)
|
||||
{
|
||||
document_data::edit_state& e = edit_state_of(d);
|
||||
if (NLOHMANN_VIEW_UNLIKELY(e.text_cap - e.text_used < n))
|
||||
{
|
||||
const std::size_t cap = (std::max)(e.text_cap * 2, e.text_used + n + 256);
|
||||
if (cap > 0xFFFFFFFFu)
|
||||
{
|
||||
throw_out_of_range(416, "edits of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE (4 GiB)
|
||||
}
|
||||
std::unique_ptr<char[]> fresh(new char[cap]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
if (e.text_used != 0)
|
||||
{
|
||||
std::memcpy(fresh.get(), e.texts.back().get(), e.text_used);
|
||||
}
|
||||
e.texts.push_back(std::move(fresh));
|
||||
e.text_cap = cap;
|
||||
e.bytes += cap;
|
||||
d.base[2] = e.texts.back().get();
|
||||
}
|
||||
const auto off = static_cast<std::uint32_t>(e.text_used);
|
||||
if (n != 0)
|
||||
{
|
||||
std::memcpy(e.texts.back().get() + e.text_used, s, n);
|
||||
}
|
||||
e.text_used += n;
|
||||
return off;
|
||||
}
|
||||
|
||||
/// the capacity in nodes of the block of a moved container (0: a fixed
|
||||
/// sequence, the elements of a new value)
|
||||
inline std::size_t moved_capacity(const document_data& d, const node* n) noexcept
|
||||
{
|
||||
return d.edits->moved_cap[n->off];
|
||||
}
|
||||
|
||||
/// let container n take its elements from `seq` (header node first)
|
||||
inline void set_moved(document_data& d, node* n, node* seq, std::size_t cap)
|
||||
{
|
||||
document_data::edit_state& e = edit_state_of(d);
|
||||
if ((n->flags & node_flags::moved) != 0)
|
||||
{
|
||||
e.moved[n->off] = seq;
|
||||
e.moved_cap[n->off] = cap;
|
||||
return;
|
||||
}
|
||||
if (e.moved.size() >= 0xFFFFFFFFu)
|
||||
{
|
||||
throw_out_of_range(416, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
|
||||
}
|
||||
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
|
||||
{
|
||||
// both grow before either changes, so that the push_backs cannot throw
|
||||
e.moved.reserve((2 * e.moved.size()) + 16);
|
||||
e.moved_cap.reserve((2 * e.moved.size()) + 16);
|
||||
}
|
||||
e.moved.push_back(seq);
|
||||
e.moved_cap.push_back(cap);
|
||||
n->off = static_cast<std::uint32_t>(e.moved.size() - 1);
|
||||
n->flags = static_cast<std::uint8_t>(n->flags | node_flags::moved | node_flags::is_new);
|
||||
}
|
||||
|
||||
/// Make the elements of container n a growable block with room for `extra`
|
||||
/// more nodes, and return its header. The entries link to the existing
|
||||
/// values, which stay where they are. A block that grows is copied (its old
|
||||
/// space is not reused).
|
||||
inline node* block_of(document_data& d, node* n, std::size_t extra)
|
||||
{
|
||||
if ((n->flags & node_flags::moved) != 0 && moved_capacity(d, n) != 0)
|
||||
{
|
||||
node* const h = d.edits->moved[n->off];
|
||||
if (h->next + extra <= moved_capacity(d, n))
|
||||
{
|
||||
return h;
|
||||
}
|
||||
const std::size_t cap = (std::max)(2 * moved_capacity(d, n), h->next + extra);
|
||||
node* const nh = alloc_nodes(d, cap);
|
||||
std::memcpy(nh, h, h->next * sizeof(node));
|
||||
set_moved(d, n, nh, cap);
|
||||
return nh;
|
||||
}
|
||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||
const std::size_t used = 1 + (static_cast<std::size_t>(n->len) * (object ? 2 : 1));
|
||||
const std::size_t cap = used + extra;
|
||||
node* const h = alloc_nodes(d, cap);
|
||||
*h = node{};
|
||||
h->kind = n->kind;
|
||||
h->len = n->len;
|
||||
h->next = static_cast<std::uint32_t>(used);
|
||||
node* o = h + 1;
|
||||
for (const node* c = d.first_child_edited(n), *e = d.child_end_edited(n); c != e;)
|
||||
{
|
||||
if (object)
|
||||
{
|
||||
*o++ = *c++; // the key
|
||||
}
|
||||
make_link(*o, document_data::deref(c));
|
||||
++o;
|
||||
c = document_data::after(c);
|
||||
}
|
||||
set_moved(d, n, h, cap);
|
||||
return h;
|
||||
}
|
||||
|
||||
/// The container whose elements include `target`; nullptr for the root, for
|
||||
/// a value that is no longer part of the document, and for a value that is
|
||||
/// only reached through a link. Values never move between allocations, so
|
||||
/// the path to `target` stays inside the allocation that holds it (the parsed
|
||||
/// index, or one new value), where the extent of each container (`next`)
|
||||
/// still covers its original subtree.
|
||||
inline node* find_parent(const document_data& d, const node* target)
|
||||
{
|
||||
const std::less<const node*> lt;
|
||||
const node* lo = d.tape;
|
||||
const node* hi = d.tape + d.tape_size;
|
||||
const node* c = d.tape;
|
||||
if (lt(target, lo) || !lt(target, hi))
|
||||
{
|
||||
if (!d.edits)
|
||||
{
|
||||
return nullptr; // LCOV_EXCL_LINE (nodes outside the index exist only after edits)
|
||||
}
|
||||
auto it = d.edits->regions.upper_bound(target);
|
||||
if (it == d.edits->regions.begin())
|
||||
{
|
||||
return nullptr; // LCOV_EXCL_LINE (an array/object with elements is in the index or a new value)
|
||||
}
|
||||
--it;
|
||||
lo = it->first;
|
||||
hi = lo + lo->next;
|
||||
if (!lt(target, hi))
|
||||
{
|
||||
return nullptr; // LCOV_EXCL_LINE (a single-node value, reached through a link)
|
||||
}
|
||||
// the root of a new value is the element sequence of its owner, or a linked value
|
||||
c = it->second != nullptr ? it->second : lo;
|
||||
}
|
||||
if (target == lo)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
for (;;)
|
||||
{
|
||||
if (!is_container(*c))
|
||||
{
|
||||
return nullptr; // LCOV_EXCL_LINE (the value is inside c)
|
||||
}
|
||||
const bool object = c->kind == static_cast<std::uint8_t>(value_t::object);
|
||||
const node* down = nullptr;
|
||||
for (const node* p = d.first_child_edited(c), *e = d.child_end_edited(c); p != e;)
|
||||
{
|
||||
const node* const at = object ? p + 1 : p;
|
||||
const node* const v = document_data::deref(at);
|
||||
if (v == target)
|
||||
{
|
||||
return const_cast<node*>(c); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to the document
|
||||
}
|
||||
if (is_container(*v) && !lt(v, lo) && lt(v, target) && lt(target, v + v->next))
|
||||
{
|
||||
down = v;
|
||||
break;
|
||||
}
|
||||
p = document_data::after(at);
|
||||
}
|
||||
if (down == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
c = down;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -30,6 +30,11 @@ namespace view
|
||||
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
|
||||
}
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const std::string& msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr));
|
||||
}
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(int id, const std::string& msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
|
||||
|
||||
@@ -0,0 +1,602 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint8_t, uint16_t, uint32_t, uint64_t
|
||||
#include <cstring> // memcmp, memcpy
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/errors.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
#include <nlohmann/detail/view/number.hpp>
|
||||
#include <nlohmann/detail/view/object_index.hpp>
|
||||
#include <nlohmann/detail/view/scan.hpp>
|
||||
|
||||
// Images: a document stored so that loading it needs no parsing.
|
||||
//
|
||||
// Layout (little-endian): a 64-byte header, the nodes, the text (the source,
|
||||
// followed by the number tokens written by edits), a NUL, the decoded strings
|
||||
// (followed by the strings written by edits), a NUL. The idea is that of
|
||||
// zero-copy formats such as FlatBuffers (https://github.com/google/flatbuffers)
|
||||
// and YaFF (https://github.com/yandex/yaff); no code is taken from them.
|
||||
// check_image follows the idea of FlatBuffers' Verifier (bounds and
|
||||
// structure) and also checks what the parser guarantees about strings and
|
||||
// numbers, so that reading and serializing a checked image is safe and yields
|
||||
// valid JSON.
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
namespace view
|
||||
{
|
||||
|
||||
/// how load() checks an image
|
||||
enum class image_check
|
||||
{
|
||||
/// everything the parser guarantees: structure and bounds, strings (valid
|
||||
/// UTF-8; source strings without quotes, backslashes, and control
|
||||
/// characters), and numbers (well-formed, matching the stored values)
|
||||
full,
|
||||
/// structure and bounds only: reading and serializing are safe, but a
|
||||
/// crafted image can yield invalid UTF-8, strings that serialize to
|
||||
/// invalid JSON, or numbers that differ from their text
|
||||
bounds,
|
||||
/// none: for images from a trusted source only (a damaged image is
|
||||
/// undefined behavior)
|
||||
none,
|
||||
};
|
||||
|
||||
struct image_header
|
||||
{
|
||||
std::array<char, 4> magic; ///< "NJVI"
|
||||
std::uint32_t version; ///< 1
|
||||
std::uint64_t node_count;
|
||||
std::uint64_t text_size;
|
||||
std::uint64_t arena_size;
|
||||
std::array<std::uint64_t, 4> reserved; ///< zero (for later versions)
|
||||
};
|
||||
static_assert(sizeof(image_header) == 64, "the image header must be 64 bytes");
|
||||
|
||||
constexpr std::uint32_t image_version = 1;
|
||||
|
||||
/// the largest node count and text or string size of an image (as for parsed
|
||||
/// documents, offsets and counts must fit 32 bits)
|
||||
constexpr std::uint64_t image_limit = 0xFFFFFFF0u;
|
||||
|
||||
/// Copy the current structure of an edited document into nodes in document
|
||||
/// order, as the parser would have written them. Text written by edits is
|
||||
/// appended to text_tail (number tokens) and arena_tail (strings); floats that
|
||||
/// are not finite become null, as dump() writes them.
|
||||
inline void compact_nodes(const document_data& d, std::size_t arena_size, std::vector<node>& out, std::string& text_tail, std::string& arena_tail)
|
||||
{
|
||||
struct frame
|
||||
{
|
||||
const node* cur;
|
||||
const node* end;
|
||||
std::size_t index; ///< the container's node in out
|
||||
std::uint32_t count;
|
||||
bool object;
|
||||
};
|
||||
std::vector<frame> stack;
|
||||
const auto string_node = [&](const node & s)
|
||||
{
|
||||
node r = s;
|
||||
r.extra = 0;
|
||||
r.flags = static_cast<std::uint8_t>(s.flags & node_flags::storage);
|
||||
if (r.flags == node_flags::edited)
|
||||
{
|
||||
r.off = static_cast<std::uint32_t>(arena_size + arena_tail.size());
|
||||
arena_tail.append(d.str(s), s.len);
|
||||
r.flags = node_flags::escaped;
|
||||
}
|
||||
return r;
|
||||
};
|
||||
const auto emit = [&](const node * v)
|
||||
{
|
||||
node r = *v;
|
||||
switch (static_cast<value_t>(v->kind))
|
||||
{
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
r.flags = 0;
|
||||
r.extra = 0;
|
||||
r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off;
|
||||
r.len = 0; // counted below
|
||||
r.next = 0; // set when the container is complete
|
||||
stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == static_cast<std::uint8_t>(value_t::object)});
|
||||
break;
|
||||
case value_t::string:
|
||||
r = string_node(*v);
|
||||
break;
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
if ((v->flags & node_flags::storage) == node_flags::edited)
|
||||
{
|
||||
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
||||
text_tail.append(d.str(*v), number_length(*v));
|
||||
}
|
||||
r.flags = 0;
|
||||
break;
|
||||
case value_t::number_float:
|
||||
if ((v->flags & node_flags::storage) == node_flags::edited)
|
||||
{
|
||||
const char* const t = d.str(*v);
|
||||
if (t[0] == 'n' || t[0] == 'i' || (v->len > 1 && t[1] == 'i'))
|
||||
{
|
||||
r = node{}; // nan and infinity: null, as dump() writes them
|
||||
r.kind = static_cast<std::uint8_t>(value_t::null);
|
||||
break;
|
||||
}
|
||||
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
||||
text_tail.append(t, v->len);
|
||||
r.extra = 0xFFFFu; // the digit layout is not recorded
|
||||
}
|
||||
r.flags = 0;
|
||||
break;
|
||||
case value_t::boolean:
|
||||
r.flags = static_cast<std::uint8_t>(v->flags & node_flags::is_true);
|
||||
break;
|
||||
case value_t::null:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
r.flags = 0;
|
||||
break;
|
||||
}
|
||||
out.push_back(r);
|
||||
};
|
||||
emit(d.tape);
|
||||
while (!stack.empty())
|
||||
{
|
||||
frame& top = stack.back();
|
||||
if (top.cur == top.end)
|
||||
{
|
||||
node& c = out[top.index];
|
||||
c.len = top.count;
|
||||
c.next = static_cast<std::uint32_t>(out.size() - top.index);
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
++top.count;
|
||||
const node* v = nullptr;
|
||||
if (top.object)
|
||||
{
|
||||
out.push_back(string_node(*top.cur));
|
||||
v = document_data::deref(top.cur + 1);
|
||||
top.cur = document_data::after(top.cur + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
v = document_data::deref(top.cur);
|
||||
top.cur = document_data::after(top.cur);
|
||||
}
|
||||
emit(v); // may grow the stack (top is not used afterwards)
|
||||
}
|
||||
}
|
||||
|
||||
/// the document as an image
|
||||
inline std::vector<std::uint8_t> save_image(const document_data& d)
|
||||
{
|
||||
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||
#endif
|
||||
const std::size_t arena_size = d.arena_size;
|
||||
const node* nodes = d.tape;
|
||||
std::size_t count = d.tape_size;
|
||||
std::vector<node> compacted;
|
||||
std::string text_tail;
|
||||
std::string arena_tail;
|
||||
if (d.edits)
|
||||
{
|
||||
compact_nodes(d, arena_size, compacted, text_tail, arena_tail);
|
||||
nodes = compacted.data();
|
||||
count = compacted.size();
|
||||
}
|
||||
const std::size_t text_size = d.size + text_tail.size();
|
||||
const std::size_t total_arena = arena_size + arena_tail.size();
|
||||
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
|
||||
{
|
||||
// LCOV_EXCL_START (4 GiB)
|
||||
throw_out_of_range(416, "images of 4 GiB or more are not supported by json_document");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
image_header h{};
|
||||
h.magic = {{'N', 'J', 'V', 'I'}};
|
||||
h.version = image_version;
|
||||
h.node_count = count;
|
||||
h.text_size = text_size;
|
||||
h.arena_size = total_arena;
|
||||
std::vector<std::uint8_t> image(sizeof(h) + (count * sizeof(node)) + text_size + 1 + total_arena + 1);
|
||||
std::uint8_t* o = image.data();
|
||||
std::memcpy(o, &h, sizeof(h));
|
||||
o += sizeof(h);
|
||||
std::memcpy(o, nodes, count * sizeof(node));
|
||||
// the hash indexes are rebuilt by load()
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
if (nodes[i].kind == static_cast<std::uint8_t>(value_t::object) && nodes[i].extra != 0)
|
||||
{
|
||||
node n = nodes[i];
|
||||
n.extra = 0;
|
||||
std::memcpy(o + (i * sizeof(node)), &n, sizeof(node));
|
||||
}
|
||||
}
|
||||
o += count * sizeof(node);
|
||||
const auto append = [&o](const char* s, std::size_t n)
|
||||
{
|
||||
if (n != 0)
|
||||
{
|
||||
std::memcpy(o, s, n);
|
||||
o += n;
|
||||
}
|
||||
};
|
||||
append(d.src, d.size);
|
||||
append(text_tail.data(), text_tail.size());
|
||||
*o++ = 0;
|
||||
append(d.base[1], arena_size);
|
||||
append(arena_tail.data(), arena_tail.size());
|
||||
*o = 0;
|
||||
return image;
|
||||
}
|
||||
|
||||
/// whether a number node matches its token the way the parser records it
|
||||
/// (after the bounds check)
|
||||
inline bool check_number(const node& n, const unsigned char* text)
|
||||
{
|
||||
const std::size_t len = number_length(n);
|
||||
const unsigned char* const s = text + n.off;
|
||||
const unsigned char* const e = s + len;
|
||||
const unsigned char* p = s;
|
||||
const bool negative = *p == '-';
|
||||
p += negative ? 1 : 0;
|
||||
const unsigned char* const int_start = p;
|
||||
if (p == e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (*p == '0')
|
||||
{
|
||||
++p;
|
||||
}
|
||||
else if (*p >= '1' && *p <= '9')
|
||||
{
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const auto int_digits = static_cast<std::size_t>(p - int_start);
|
||||
std::size_t frac_digits = 0;
|
||||
bool is_float = false;
|
||||
if (p != e && *p == '.')
|
||||
{
|
||||
const unsigned char* const f0 = ++p;
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
if (p == f0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
frac_digits = static_cast<std::size_t>(p - f0);
|
||||
is_float = true;
|
||||
}
|
||||
std::int64_t exponent = 0;
|
||||
if (p != e && (*p | 0x20u) == 'e')
|
||||
{
|
||||
++p;
|
||||
const bool exp_negative = p != e && *p == '-';
|
||||
p += (p != e && (*p == '+' || *p == '-')) ? 1 : 0;
|
||||
if (p == e || !is_digit(*p))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
exponent = exponent < 100000 ? (exponent * 10) + (*p - '0') : exponent;
|
||||
++p;
|
||||
}
|
||||
exponent = exp_negative ? -exponent : exponent;
|
||||
is_float = true;
|
||||
}
|
||||
if (p != e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (n.kind == static_cast<std::uint8_t>(value_t::number_float))
|
||||
{
|
||||
// the digit layout the parser records (or "many", as compaction
|
||||
// writes it), and a finite value
|
||||
const auto layout = static_cast<std::uint16_t>((int_digits < 255 ? int_digits : 255) | ((frac_digits < 255 ? frac_digits : 255) << 8u));
|
||||
if (n.extra != layout && n.extra != 0xFFFFu)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// parse() rejects floats that overflow; as there, only a number whose
|
||||
// magnitude could reach 1e308 needs the conversion
|
||||
if (static_cast<std::int64_t>(int_digits) + exponent > 300)
|
||||
{
|
||||
const auto v = float_value<double>(reinterpret_cast<const char*>(s), n); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
return v <= (std::numeric_limits<double>::max)() && v >= -(std::numeric_limits<double>::max)();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// integers: the token's value is the stored one; number_integer nodes of
|
||||
// edits can be non-negative (as basic_json keeps the type of a value)
|
||||
const bool integer = n.kind == static_cast<std::uint8_t>(value_t::number_integer);
|
||||
if (is_float || int_digits > 20 || (negative && !integer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// (at most 19 digits cannot overflow; 20 digits are compared with 2^64 - 1)
|
||||
if (int_digits == 20 && std::memcmp(int_start, "18446744073709551615", 20) > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::uint64_t m = 0;
|
||||
for (const unsigned char* d = int_start; d != int_start + int_digits; ++d)
|
||||
{
|
||||
m = (m * 10) + static_cast<std::uint64_t>(*d - '0');
|
||||
}
|
||||
if (integer && m > (negative ? std::uint64_t{1} << 63u : (std::uint64_t{1} << 63u) - 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return integer_bits(n) == (negative ? 0 - m : m);
|
||||
}
|
||||
|
||||
/// Check the nodes of a loaded image against its text and decoded strings:
|
||||
/// kinds, flags, and `extra`; extents and element counts of arrays and
|
||||
/// objects; keys; bounds; string contents (source strings as the parser
|
||||
/// leaves them: no quotes, backslashes, or control characters; all strings
|
||||
/// valid UTF-8); and number tokens.
|
||||
inline bool check_image(const node* nodes, std::size_t count, const unsigned char* text, std::size_t text_size,
|
||||
const unsigned char* arena, std::size_t arena_size, bool full)
|
||||
{
|
||||
struct frame
|
||||
{
|
||||
std::size_t end;
|
||||
std::uint32_t len;
|
||||
std::uint32_t seen;
|
||||
bool object;
|
||||
bool expect_key;
|
||||
};
|
||||
std::vector<frame> stack;
|
||||
const auto check_string = [&](const node & n) -> bool
|
||||
{
|
||||
if ((n.flags & ~node_flags::escaped) != 0 || n.extra != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const bool decoded = (n.flags & node_flags::escaped) != 0;
|
||||
const unsigned char* const base = decoded ? arena : text;
|
||||
const std::size_t limit = decoded ? arena_size : text_size;
|
||||
if (n.off > limit || n.len > limit - n.off)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!full)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const unsigned char* const b = base + n.off;
|
||||
return decoded ? valid_utf8_prefix(b, n.len) == n.len : scan_string_run(b, b + n.len) == b + n.len;
|
||||
};
|
||||
// bounds of a number token; the recorded digit layout must lie within it
|
||||
const auto number_in_bounds = [&](const node & n) -> bool
|
||||
{
|
||||
const std::size_t len = number_length(n);
|
||||
if (len == 0 || n.off > text_size || len > text_size - n.off)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (n.kind != static_cast<std::uint8_t>(value_t::number_float))
|
||||
{
|
||||
return (n.extra >> 8u) == 0;
|
||||
}
|
||||
// float_value() reads the sign, the integer digits, and the point and
|
||||
// fraction digits the layout records (a layout of more than 19 digits
|
||||
// means the general conversion, which stays within the token)
|
||||
const std::size_t int_digits = n.extra & 0xFFu;
|
||||
const std::size_t frac_digits = n.extra >> 8u;
|
||||
const std::size_t need = (text[n.off] == '-' ? 1u : 0u) + int_digits + (frac_digits != 0 ? frac_digits + 1 : 0);
|
||||
return int_digits + frac_digits > 19 || need <= len;
|
||||
};
|
||||
std::size_t i = 0;
|
||||
for (;;)
|
||||
{
|
||||
// close finished arrays and objects
|
||||
while (!stack.empty() && i == stack.back().end)
|
||||
{
|
||||
const frame f = stack.back();
|
||||
if (f.seen != f.len || (f.object && !f.expect_key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
stack.pop_back();
|
||||
if (!stack.empty())
|
||||
{
|
||||
++stack.back().seen;
|
||||
stack.back().expect_key = true;
|
||||
}
|
||||
}
|
||||
if (i == count)
|
||||
{
|
||||
return stack.empty();
|
||||
}
|
||||
if (i != 0 && stack.empty())
|
||||
{
|
||||
return false; // nodes after the root
|
||||
}
|
||||
const node& n = nodes[i];
|
||||
if (!stack.empty() && stack.back().object && stack.back().expect_key)
|
||||
{
|
||||
if (n.kind != static_cast<std::uint8_t>(value_t::string) || !check_string(n))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
stack.back().expect_key = false;
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
bool complete = true;
|
||||
switch (static_cast<value_t>(n.kind))
|
||||
{
|
||||
case value_t::null:
|
||||
// (the offset of a literal is read to size the output of dump())
|
||||
if (n.flags != 0 || n.extra != 0 || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case value_t::boolean:
|
||||
if ((n.flags & ~node_flags::is_true) != 0 || n.extra != 0 || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case value_t::string:
|
||||
if (!check_string(n))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
if (n.flags != 0 || !number_in_bounds(n) || (full && !check_number(n, text)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case value_t::array:
|
||||
case value_t::object:
|
||||
{
|
||||
const std::size_t limit = stack.empty() ? count : stack.back().end;
|
||||
if (n.flags != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
stack.push_back(frame{i + n.next, n.len, 0, n.kind == static_cast<std::uint8_t>(value_t::object), true});
|
||||
complete = false;
|
||||
break;
|
||||
}
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
++i;
|
||||
if (complete && !stack.empty())
|
||||
{
|
||||
++stack.back().seen;
|
||||
stack.back().expect_key = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
|
||||
{
|
||||
throw_parse_error(116, concat("invalid json_document image: ", what));
|
||||
}
|
||||
|
||||
/// Read an image into d. The text and the decoded strings stay in the image;
|
||||
/// the nodes are copied (so that they are aligned, and edits can change them).
|
||||
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
|
||||
{
|
||||
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||
#endif
|
||||
if (image == nullptr || size < sizeof(image_header))
|
||||
{
|
||||
throw_invalid_image("too short");
|
||||
}
|
||||
image_header h{};
|
||||
std::memcpy(&h, image, sizeof(h));
|
||||
// (the reserved fields are for later versions)
|
||||
if (std::memcmp(h.magic.data(), "NJVI", 4) != 0 || h.version != image_version
|
||||
|| (h.reserved[0] | h.reserved[1] | h.reserved[2] | h.reserved[3]) != 0)
|
||||
{
|
||||
throw_invalid_image("unknown format");
|
||||
}
|
||||
const std::size_t room = size - sizeof(h);
|
||||
if (h.node_count == 0 || h.node_count > room / sizeof(node) || h.node_count >= image_limit || h.text_size >= image_limit || h.arena_size >= image_limit)
|
||||
{
|
||||
throw_invalid_image("sizes out of range");
|
||||
}
|
||||
const auto count = static_cast<std::size_t>(h.node_count);
|
||||
const auto text_size = static_cast<std::size_t>(h.text_size);
|
||||
const auto arena_size = static_cast<std::size_t>(h.arena_size);
|
||||
const std::size_t text_at = sizeof(h) + (count * sizeof(node));
|
||||
// the text, a NUL, the decoded strings, a NUL, and nothing after them
|
||||
if (size - text_at < 2 || text_size > size - text_at - 2 || arena_size != size - text_at - text_size - 2
|
||||
|| image[text_at + text_size] != 0 || image[size - 1] != 0)
|
||||
{
|
||||
throw_invalid_image("sizes out of range");
|
||||
}
|
||||
|
||||
d.discarded = true;
|
||||
d.edits.reset();
|
||||
d.base[2] = nullptr;
|
||||
d.owned.clear();
|
||||
if (d.owned_image.empty() || image != d.owned_image.data())
|
||||
{
|
||||
d.owned_image.clear();
|
||||
}
|
||||
d.arena.clear();
|
||||
d.indexes.clear();
|
||||
d.index_slots.clear();
|
||||
d.large_objects.clear();
|
||||
d.tape_size = 0;
|
||||
d.reserve(count);
|
||||
std::memcpy(d.tape, image + sizeof(h), count * sizeof(node));
|
||||
d.tape_size = count;
|
||||
const std::uint8_t* const text = image + text_at;
|
||||
const std::uint8_t* const arena = text + text_size + 1;
|
||||
d.src = reinterpret_cast<const char*>(text); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
d.size = text_size;
|
||||
d.base[0] = d.src;
|
||||
d.base[1] = reinterpret_cast<const char*>(arena); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
d.arena_size = arena_size;
|
||||
if (check != image_check::none && !check_image(d.tape, count, text, text_size, arena, arena_size, check == image_check::full))
|
||||
{
|
||||
throw_invalid_image("the check failed");
|
||||
}
|
||||
// the hash indexes of large objects, as after parsing
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
node& n = d.tape[i];
|
||||
if (n.kind == static_cast<std::uint8_t>(value_t::object))
|
||||
{
|
||||
n.extra = 0;
|
||||
if (n.len >= document_data::index_min_members)
|
||||
{
|
||||
d.large_objects.push_back(static_cast<std::uint32_t>(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
build_object_indexes(d);
|
||||
d.discarded = false;
|
||||
}
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -73,7 +73,7 @@ class view_iterator
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE View operator*() const noexcept
|
||||
{
|
||||
return View(m_doc, m_pos + m_value_offset);
|
||||
return View(m_doc, View::navigation::value(m_pos + m_value_offset));
|
||||
}
|
||||
|
||||
pointer operator->() const noexcept
|
||||
|
||||
@@ -84,18 +84,20 @@ class short_key
|
||||
|
||||
/// the key node of the first member of an object with the given key, or
|
||||
/// nullptr; most keys are rejected by their length, from the index alone
|
||||
inline const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
|
||||
template<bool Editable>
|
||||
const node* find_member(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(object->extra != 0))
|
||||
using nav = navigation<Editable>;
|
||||
if (NLOHMANN_VIEW_UNLIKELY(object->extra != 0) && (!Editable || (object->flags & node_flags::moved) == 0))
|
||||
{
|
||||
return find_indexed(d, object, key, n); // a large object
|
||||
return find_indexed(d, object, key, n); // a large object (whose members have not been edited)
|
||||
}
|
||||
const node* const end = document_data::child_end(object);
|
||||
const node* const end = nav::end(d, object);
|
||||
const auto* const k = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
if (NLOHMANN_VIEW_LIKELY(n <= 16))
|
||||
{
|
||||
const short_key probe(k, n);
|
||||
for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1))
|
||||
for (const node* m = nav::first(d, object); m != end; m = document_data::after(m + 1))
|
||||
{
|
||||
if (m->len == n && probe.matches(reinterpret_cast<const unsigned char*>(d.str(*m)))) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
{
|
||||
@@ -104,7 +106,7 @@ inline const node* find_member(const document_data& d, const node* object, const
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
for (const node* m = document_data::first_child(object); m != end; m = document_data::after(m + 1))
|
||||
for (const node* m = nav::first(d, object); m != end; m = document_data::after(m + 1))
|
||||
{
|
||||
if (m->len == n && std::memcmp(d.str(*m), key, n) == 0)
|
||||
{
|
||||
@@ -114,10 +116,16 @@ inline const node* find_member(const document_data& d, const node* object, const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// the element of an array at an index below its size
|
||||
inline const node* element_at(const node* array, std::size_t idx) noexcept
|
||||
/// the entry of the element of an array at an index below its size (a link
|
||||
/// in the moved sequences of editable documents)
|
||||
template<bool Editable>
|
||||
const node* element_at(const document_data& d, const node* array, std::size_t idx) noexcept
|
||||
{
|
||||
const node* e = document_data::first_child(array);
|
||||
const node* e = navigation<Editable>::first(d, array);
|
||||
if (Editable && (array->flags & node_flags::moved) != 0 && d.edits->moved_cap[array->off] != 0)
|
||||
{
|
||||
return e + idx; // a growable block: one link per element
|
||||
}
|
||||
for (std::size_t i = 0; i < idx; ++i)
|
||||
{
|
||||
e = document_data::after(e);
|
||||
@@ -125,13 +133,14 @@ inline const node* element_at(const node* array, std::size_t idx) noexcept
|
||||
return e;
|
||||
}
|
||||
|
||||
/// the last element of a non-empty array, or the key of the last member of a
|
||||
/// non-empty object
|
||||
inline const node* last_child(const node* container) noexcept
|
||||
/// the entry of the last element of a non-empty array, or the key of the
|
||||
/// last member of a non-empty object
|
||||
template<bool Editable>
|
||||
const node* last_child(const document_data& d, const node* container) noexcept
|
||||
{
|
||||
const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0;
|
||||
const node* const end = document_data::child_end(container);
|
||||
const node* last = document_data::first_child(container);
|
||||
const node* const end = navigation<Editable>::end(d, container);
|
||||
const node* last = navigation<Editable>::first(d, container);
|
||||
for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset))
|
||||
{
|
||||
last = c;
|
||||
|
||||
@@ -34,19 +34,28 @@ iterative, so the nesting depth is limited by memory only, as for parse().
|
||||
Without a lexer the handler records no source positions
|
||||
(JSON_DIAGNOSTIC_POSITIONS).
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
template<typename BasicJsonType, bool Editable>
|
||||
BasicJsonType materialize(const document_data& d, const node* n)
|
||||
{
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>>;
|
||||
using nav = navigation<Editable>;
|
||||
|
||||
struct frame
|
||||
{
|
||||
const node* pos; ///< next element, or key of the next member
|
||||
const node* end;
|
||||
bool object;
|
||||
};
|
||||
|
||||
BasicJsonType result;
|
||||
sax_t sax(result, true);
|
||||
const string_t no_token{};
|
||||
// the ends of the open containers, and whether they are objects
|
||||
std::vector<std::pair<const node*, bool>> open;
|
||||
std::vector<frame> open;
|
||||
for (;;)
|
||||
{
|
||||
// false positive: n comes from nav::value(), which never returns null for a valid index
|
||||
// @infer-ignore NULLPTR_DEREFERENCE
|
||||
switch (static_cast<value_t>(n->kind))
|
||||
{
|
||||
case value_t::object:
|
||||
@@ -61,67 +70,66 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
{
|
||||
sax.start_array(n->len);
|
||||
}
|
||||
open.emplace_back(document_data::child_end(n), object);
|
||||
n = document_data::first_child(n);
|
||||
open.push_back(frame{nav::first(d, n), nav::end(d, n), object});
|
||||
break;
|
||||
}
|
||||
case value_t::string:
|
||||
{
|
||||
string_t s(d.str(*n), n->len);
|
||||
sax.string(s);
|
||||
++n;
|
||||
break;
|
||||
}
|
||||
case value_t::number_integer:
|
||||
sax.number_integer(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(*n))));
|
||||
++n;
|
||||
break;
|
||||
case value_t::number_unsigned:
|
||||
sax.number_unsigned(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(*n)));
|
||||
++n;
|
||||
break;
|
||||
case value_t::number_float:
|
||||
sax.number_float(float_value<typename BasicJsonType::number_float_t>(d, *n), no_token);
|
||||
++n;
|
||||
break;
|
||||
case value_t::boolean:
|
||||
sax.boolean((n->flags & node_flags::is_true) != 0);
|
||||
++n;
|
||||
break;
|
||||
case value_t::null:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
sax.null();
|
||||
++n;
|
||||
break;
|
||||
}
|
||||
|
||||
// the next value: close finished containers, then read the key
|
||||
for (;;)
|
||||
{
|
||||
if (open.empty())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if (n != open.back().first)
|
||||
frame& f = open.back();
|
||||
if (f.pos == f.end)
|
||||
{
|
||||
break;
|
||||
if (f.object)
|
||||
{
|
||||
sax.end_object();
|
||||
}
|
||||
else
|
||||
{
|
||||
sax.end_array();
|
||||
}
|
||||
open.pop_back();
|
||||
continue;
|
||||
}
|
||||
if (open.back().second)
|
||||
const node* entry = f.pos;
|
||||
if (f.object)
|
||||
{
|
||||
sax.end_object();
|
||||
string_t key(d.str(*entry), entry->len);
|
||||
sax.key(key);
|
||||
++entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
sax.end_array();
|
||||
}
|
||||
open.pop_back();
|
||||
}
|
||||
if (open.back().second)
|
||||
{
|
||||
// the key of the next member
|
||||
string_t key(d.str(*n), n->len);
|
||||
sax.key(key);
|
||||
++n;
|
||||
n = nav::value(entry);
|
||||
f.pos = document_data::after(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,20 +33,27 @@ static_assert(static_cast<std::uint8_t>(value_t::null) == 0 && static_cast<std::
|
||||
struct node_flags
|
||||
{
|
||||
static constexpr std::uint8_t escaped = 1; ///< string payload lives in the decode arena, not the source
|
||||
static constexpr std::uint8_t edited = 2; ///< string or number token lives in the edit arena (editable documents)
|
||||
static constexpr std::uint8_t storage = 3; ///< mask: where a string or number token lives (index into document_data::base)
|
||||
static constexpr std::uint8_t is_true = 4; ///< boolean value
|
||||
static constexpr std::uint8_t moved = 8; ///< array/object: the elements live in a separate sequence (editable documents)
|
||||
static constexpr std::uint8_t is_new = 16; ///< written by an edit: no source position
|
||||
};
|
||||
|
||||
/// kind of an entry of an edited sequence that stands for a value stored
|
||||
/// elsewhere (the address of the value's node is kept in the len/next bytes)
|
||||
constexpr std::uint8_t kind_link = 10;
|
||||
|
||||
/// One entry of the flat index, in document order. An object's members are
|
||||
/// stored as key node followed by the value's subtree. Integers keep their
|
||||
/// converted 64-bit value in the len/next bytes (the node after a scalar is
|
||||
/// always the next one, and the token length follows from `extra`).
|
||||
struct node
|
||||
{
|
||||
std::uint8_t kind; ///< value_t
|
||||
std::uint8_t kind; ///< value_t, or kind_link
|
||||
std::uint8_t flags; ///< node_flags
|
||||
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; otherwise 0
|
||||
std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if node_flags::escaped
|
||||
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the hash index; otherwise 0
|
||||
std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved
|
||||
std::uint32_t len; ///< string: decoded bytes; float: token bytes; array/object: element count
|
||||
std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence)
|
||||
};
|
||||
@@ -57,6 +64,21 @@ NLOHMANN_VIEW_ALWAYS_INLINE bool is_container(const node& n) noexcept
|
||||
return static_cast<unsigned>(n.kind) - 1u <= 1u;
|
||||
}
|
||||
|
||||
/// the value a link node stands for
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* link_target(const node& n) noexcept
|
||||
{
|
||||
const node* t = nullptr;
|
||||
std::memcpy(static_cast<void*>(&t), reinterpret_cast<const unsigned char*>(&n) + 8, sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
return t;
|
||||
}
|
||||
|
||||
inline void make_link(node& n, const node* target) noexcept
|
||||
{
|
||||
n = node{};
|
||||
n.kind = kind_link;
|
||||
std::memcpy(reinterpret_cast<unsigned char*>(&n) + 8, static_cast<const void*>(&target), sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
}
|
||||
|
||||
/// the converted value of an integer node (stored in len/next)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t integer_bits(const node& n) noexcept
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint64_t
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <type_traits> // integral_constant
|
||||
|
||||
@@ -25,43 +26,76 @@ namespace detail
|
||||
namespace view
|
||||
{
|
||||
|
||||
/*!
|
||||
@brief locate the decimal point and the end of the mantissa of a float token
|
||||
|
||||
Also checks that the token is a JSON number. Tokens of the parser and of edits
|
||||
always are; an image loaded with image_check::bounds can hold any bytes, which
|
||||
must not reach the conversion (it expects a well-formed token).
|
||||
*/
|
||||
inline bool float_token_layout(const char* first, const char* last, std::size_t& dot, std::size_t& mantissa_end) noexcept
|
||||
{
|
||||
const auto digit = [last](const char* q)
|
||||
{
|
||||
return q != last && is_digit(static_cast<unsigned char>(*q));
|
||||
};
|
||||
const char* p = first;
|
||||
p += (p != last && *p == '-') ? 1 : 0;
|
||||
if (!digit(p) || (*p == '0' && digit(p + 1)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
while (digit(p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
dot = std::string::npos;
|
||||
if (p != last && *p == '.')
|
||||
{
|
||||
dot = static_cast<std::size_t>(p - first);
|
||||
if (!digit(++p))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
while (digit(p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
}
|
||||
mantissa_end = static_cast<std::size_t>(p - first);
|
||||
if (p != last && (*p == 'e' || *p == 'E'))
|
||||
{
|
||||
++p;
|
||||
p += (p != last && (*p == '+' || *p == '-')) ? 1 : 0;
|
||||
if (!digit(p))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
while (digit(p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
}
|
||||
return p == last;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the value of the float token of a node, as parse() converts it
|
||||
|
||||
Uses the lexer's conversion (detail::convert_float), so that the values are
|
||||
bit-identical to parse(): float and double are converted without allocation
|
||||
and independent of the locale. The digit layout recorded while parsing locates
|
||||
the decimal point and the exponent without scanning the token.
|
||||
and independent of the locale. A token that is not a JSON number (only in a
|
||||
damaged image loaded with image_check::bounds) yields 0.
|
||||
*/
|
||||
template<typename FloatType>
|
||||
NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n)
|
||||
{
|
||||
const char* const last = first + n.len;
|
||||
const std::size_t neg = first[0] == '-' ? 1 : 0;
|
||||
const std::size_t int_digits = n.extra & 0xFFu;
|
||||
const std::size_t frac_digits = n.extra >> 8u;
|
||||
std::size_t dot = std::string::npos;
|
||||
std::size_t mantissa_end = n.len;
|
||||
if (int_digits != 255 && frac_digits != 255)
|
||||
std::size_t dot = 0;
|
||||
std::size_t mantissa_end = 0;
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!float_token_layout(first, last, dot, mantissa_end)))
|
||||
{
|
||||
dot = frac_digits != 0 ? neg + int_digits : std::string::npos;
|
||||
mantissa_end = neg + int_digits + (frac_digits != 0 ? 1 + frac_digits : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// more digits than the layout records: locate them
|
||||
for (std::size_t i = 0; i < n.len; ++i)
|
||||
{
|
||||
if (first[i] == '.')
|
||||
{
|
||||
dot = i;
|
||||
}
|
||||
else if (first[i] == 'e' || first[i] == 'E')
|
||||
{
|
||||
mantissa_end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return FloatType{};
|
||||
}
|
||||
return convert_float<FloatType>(first, last, dot, mantissa_end);
|
||||
}
|
||||
@@ -92,16 +126,20 @@ NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char
|
||||
}
|
||||
if (p != e)
|
||||
{
|
||||
// [eE][+-]digits; huge exponents saturate (the parser rejected overflow)
|
||||
// [eE][+-]digits; huge exponents saturate (the parser rejected
|
||||
// overflow). The token is not read beyond e, and the digits are taken
|
||||
// as unsigned, so that a token that is not well-formed (a damaged
|
||||
// image loaded with image_check::bounds) yields a wrong value, but no
|
||||
// overflow.
|
||||
++p;
|
||||
const bool exp_negative = *p == '-';
|
||||
p += (*p == '-' || *p == '+') ? 1 : 0;
|
||||
const bool exp_negative = p != e && *p == '-';
|
||||
p += (p != e && (*p == '-' || *p == '+')) ? 1 : 0;
|
||||
std::int64_t exp_value = 0;
|
||||
for (; p != e; ++p)
|
||||
{
|
||||
if (exp_value < 0x10000000)
|
||||
{
|
||||
exp_value = (exp_value * 10) + (*p - '0');
|
||||
exp_value = (exp_value * 10) + static_cast<unsigned char>(*p - '0');
|
||||
}
|
||||
}
|
||||
q += exp_negative ? -exp_value : exp_value;
|
||||
@@ -128,11 +166,31 @@ NLOHMANN_VIEW_ALWAYS_INLINE FloatType layout_float(const unsigned char* p, const
|
||||
return decimal_to_float<FloatType>(layout_decimal(p, e, int_digits, frac_digits, limit));
|
||||
}
|
||||
|
||||
/// the value of a float set by an edit: its token (the shortest round-trip
|
||||
/// text, or "nan", "inf", "-inf") in the edit arena
|
||||
template<typename FloatType>
|
||||
NLOHMANN_VIEW_NOINLINE FloatType edited_float(const char* token, const node& n)
|
||||
{
|
||||
if (token[0] == 'n')
|
||||
{
|
||||
return std::numeric_limits<FloatType>::quiet_NaN();
|
||||
}
|
||||
if (token[0] == 'i' || (token[0] == '-' && token[1] == 'i'))
|
||||
{
|
||||
return token[0] == 'i' ? std::numeric_limits<FloatType>::infinity() : -std::numeric_limits<FloatType>::infinity();
|
||||
}
|
||||
return float_value<FloatType>(token, n);
|
||||
}
|
||||
|
||||
/// the value of the float token of a node, as parse() converts it; floats and
|
||||
/// doubles with at most 19 digits are converted from the digit layout
|
||||
template<typename FloatType>
|
||||
FloatType float_value(const document_data& d, const node& n)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY((n.flags & node_flags::storage) == node_flags::edited))
|
||||
{
|
||||
return edited_float<FloatType>(d.str(n), n);
|
||||
}
|
||||
return float_value<FloatType>(d, n, std::integral_constant<bool, has_native_float_format<FloatType>::value> {});
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,11 @@ namespace view
|
||||
{
|
||||
|
||||
/// 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>
|
||||
class output_buffer
|
||||
{
|
||||
@@ -75,17 +79,49 @@ class output_buffer
|
||||
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:
|
||||
/// 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
NLOHMANN_VIEW_NOINLINE void grow(std::size_t n)
|
||||
{
|
||||
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_end = &m_out[0] + m_out.size();
|
||||
}
|
||||
@@ -95,6 +131,105 @@ class output_buffer
|
||||
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
|
||||
struct dump_style
|
||||
{
|
||||
@@ -115,9 +250,10 @@ the library's conversion; integers are copied from the source, where they
|
||||
are canonical (except "-0", which parse() reads as 0). The walk is
|
||||
iterative, so the nesting depth is limited by memory only.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
template<typename BasicJsonType, bool Editable>
|
||||
class view_serializer
|
||||
{
|
||||
using nav = navigation<Editable>;
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
|
||||
@@ -128,6 +264,18 @@ class view_serializer
|
||||
|
||||
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
|
||||
{
|
||||
const node* pos; ///< next element, or key of the next member
|
||||
@@ -135,7 +283,9 @@ class view_serializer
|
||||
bool object;
|
||||
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;
|
||||
for (;;)
|
||||
{
|
||||
@@ -150,7 +300,7 @@ class view_serializer
|
||||
else
|
||||
{
|
||||
m_out.put(object ? '{' : '[');
|
||||
stack.push_back(frame{document_data::first_child(n), document_data::child_end(n), object, true});
|
||||
stack.push_back(frame{nav::first(m_doc, n), nav::end(m_doc, n), object, true});
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -192,19 +342,303 @@ class view_serializer
|
||||
{
|
||||
m_out.put(':');
|
||||
}
|
||||
n = f.pos + 1;
|
||||
n = nav::value(f.pos + 1);
|
||||
f.pos = document_data::after(f.pos + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
n = f.pos;
|
||||
n = nav::value(f.pos);
|
||||
f.pos = document_data::after(f.pos);
|
||||
}
|
||||
f.pos = document_data::after(n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (m_style.pretty)
|
||||
@@ -250,13 +684,13 @@ class view_serializer
|
||||
break;
|
||||
}
|
||||
case value_t::number_float:
|
||||
if (m_style.source_numbers)
|
||||
if (m_style.source_numbers && (n.flags & node_flags::storage) != node_flags::edited)
|
||||
{
|
||||
m_out.put(m_doc.str(n), n.len);
|
||||
m_out.put(m_doc.str(n), n.len); // (a float set by an edit is written as with shortest)
|
||||
}
|
||||
else
|
||||
{
|
||||
write_float(float_value<number_float_t>(m_doc, n));
|
||||
write_float_node(n);
|
||||
}
|
||||
break;
|
||||
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
|
||||
@@ -268,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()
|
||||
void write_float(number_float_t x)
|
||||
{
|
||||
@@ -299,9 +810,9 @@ class view_serializer
|
||||
{
|
||||
const char* const s = m_doc.str(n);
|
||||
m_out.put('"');
|
||||
if ((n.flags & node_flags::escaped) == 0 && !m_style.ensure_ascii)
|
||||
if ((n.flags & node_flags::storage) == 0 && !m_style.ensure_ascii)
|
||||
{
|
||||
// a string without escape sequences has nothing to escape
|
||||
// a string of the source without escape sequences has nothing to escape
|
||||
m_out.put(s, n.len);
|
||||
}
|
||||
else if (m_style.ensure_ascii)
|
||||
@@ -315,7 +826,9 @@ class view_serializer
|
||||
m_out.put('"');
|
||||
}
|
||||
|
||||
/// as serializer::dump_escaped() for valid UTF-8 (the view has no other)
|
||||
/// as serializer::dump_escaped(); strings of a document are valid UTF-8,
|
||||
/// except in a damaged image loaded with image_check::bounds, for which
|
||||
/// this throws what basic_json::dump() throws for the string
|
||||
template<bool EnsureAscii>
|
||||
void write_escaped(const unsigned char* s, std::size_t n)
|
||||
{
|
||||
@@ -339,12 +852,13 @@ class view_serializer
|
||||
}
|
||||
std::uint32_t codepoint = s[i];
|
||||
std::size_t len = 1;
|
||||
if (codepoint >= 0xC0)
|
||||
if (codepoint >= 0x80)
|
||||
{
|
||||
len = 2;
|
||||
if (codepoint >= 0xE0)
|
||||
len = validate_one_utf8(s + i, n - i);
|
||||
if (NLOHMANN_VIEW_UNLIKELY(len == 0))
|
||||
{
|
||||
len = codepoint >= 0xF0 ? 4 : 3;
|
||||
invalid_utf8(s, n);
|
||||
return;
|
||||
}
|
||||
codepoint &= 0xFFu >> (len + 1);
|
||||
for (std::size_t k = 1; k < len; ++k)
|
||||
@@ -357,6 +871,13 @@ class view_serializer
|
||||
}
|
||||
}
|
||||
|
||||
/// throw what basic_json::dump() throws for a string that is not valid UTF-8
|
||||
NLOHMANN_VIEW_NOINLINE static void invalid_utf8(const unsigned char* s, std::size_t n)
|
||||
{
|
||||
const string_t dumped = BasicJsonType(string_t(reinterpret_cast<const char*>(s), n)).dump(); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
static_cast<void>(dumped);
|
||||
}
|
||||
|
||||
template<bool EnsureAscii>
|
||||
void write_codepoint(std::uint32_t codepoint, const unsigned char* bytes, std::size_t len)
|
||||
{
|
||||
|
||||
+88
-329
@@ -613,210 +613,100 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// constructor for rvalue binary arrays (internal type)
|
||||
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
|
||||
|
||||
private:
|
||||
// raw, allocation-free transfer of m_data from src to dst: no
|
||||
// set_parents()/assert_invariant() (the former is O(#children) per
|
||||
// call under JSON_DIAGNOSTICS, which would make the walk below
|
||||
// quadratic); dst takes ownership, src is left as value_t::null.
|
||||
static void take(basic_json& dst, basic_json& src) noexcept
|
||||
{
|
||||
dst.m_data.m_type = src.m_data.m_type;
|
||||
dst.m_data.m_value = src.m_data.m_value;
|
||||
src.m_data.m_type = value_t::null;
|
||||
}
|
||||
|
||||
// true if v is not an array/object, or is an already-empty one
|
||||
static bool has_no_children(const basic_json& v) noexcept
|
||||
{
|
||||
switch (v.m_data.m_type)
|
||||
{
|
||||
case value_t::array:
|
||||
return v.m_data.m_value.array->empty();
|
||||
case value_t::object:
|
||||
return v.m_data.m_value.object->empty();
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static basic_json& last_child(basic_json& v)
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
return v.m_data.m_value.array->back();
|
||||
}
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
return v.m_data.m_value.object->rbegin()->second;
|
||||
}
|
||||
|
||||
// removes the last child of a non-empty array/object v; this never
|
||||
// allocates, and since it is only ever called when that child is a
|
||||
// scalar or an already-empty array/object, destroying it never
|
||||
// recurses more than one level deep (see destroy() below)
|
||||
static void pop_last_child(basic_json& v)
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
v.m_data.m_value.array->pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
// erase() needs a forward iterator, so std::prev(end()) is
|
||||
// used here rather than rbegin() (see last_child() above)
|
||||
v.m_data.m_value.object->erase(std::prev(v.m_data.m_value.object->end()));
|
||||
}
|
||||
}
|
||||
|
||||
// deallocates the (already empty) array/object held by v; this is
|
||||
// the same allocator-based free the old recursive implementation
|
||||
// used, just factored out so every level of the walk in destroy()
|
||||
// can share it
|
||||
static void free_container(basic_json& v) noexcept
|
||||
{
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_value.array->empty());
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
JSON_ASSERT(v.m_data.m_value.object->empty());
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
||||
}
|
||||
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
|
||||
}
|
||||
|
||||
public:
|
||||
void destroy_string() noexcept
|
||||
{
|
||||
if (string == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<string_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||
}
|
||||
|
||||
void destroy_binary() noexcept
|
||||
{
|
||||
if (binary == nullptr)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
AllocatorType<binary_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||
}
|
||||
|
||||
// t must be value_t::array or value_t::object
|
||||
void destroy_container(value_t t) noexcept
|
||||
void destroy(value_t t)
|
||||
{
|
||||
if (
|
||||
(t == value_t::object && object == nullptr) ||
|
||||
(t == value_t::array && array == nullptr)
|
||||
(t == value_t::array && array == nullptr) ||
|
||||
(t == value_t::string && string == nullptr) ||
|
||||
(t == value_t::binary && binary == nullptr)
|
||||
)
|
||||
{
|
||||
// not initialized (e.g., due to exception in the ctor)
|
||||
return;
|
||||
}
|
||||
|
||||
// Destroy the tree without recursing per nesting level and
|
||||
// without any heap allocation: a heap-allocated flattening
|
||||
// stack (the previous implementation) can itself throw
|
||||
// bad_alloc, which would escape this noexcept destructor and
|
||||
// terminate the program (#5135).
|
||||
//
|
||||
// Instead, walk down the "last child" chain, reversing links
|
||||
// as we go: cur is the container currently being emptied,
|
||||
// and prev is its parent (value_t::null when there is none).
|
||||
// Each parent's last child slot doubles as storage for that
|
||||
// parent's own parent link while we are below it, so no
|
||||
// extra memory is needed. We only ever remove a child once
|
||||
// it is a scalar or an empty array/object, which neither
|
||||
// allocates nor recurses more than one level deep.
|
||||
//
|
||||
// This json_value is not itself a basic_json, so the
|
||||
// top-level container is first moved into a local stand-in
|
||||
// ("cur"); a default-constructed basic_json has a null
|
||||
// pointer in its m_value (see data::m_value's initializer),
|
||||
// so swapping it with *this leaves this union's own pointer
|
||||
// null, and it is never looked at or freed a second time.
|
||||
basic_json cur;
|
||||
cur.m_data.m_type = t;
|
||||
using std::swap;
|
||||
swap(cur.m_data.m_value, *this);
|
||||
|
||||
basic_json prev; // value_t::null: no parent
|
||||
|
||||
while (true)
|
||||
if (t == value_t::array || t == value_t::object)
|
||||
{
|
||||
if (has_no_children(cur))
|
||||
// flatten the current json_value to a heap-allocated stack
|
||||
std::vector<basic_json> stack;
|
||||
|
||||
// move the top-level items to stack
|
||||
if (t == value_t::array)
|
||||
{
|
||||
if (prev.m_data.m_type == value_t::null)
|
||||
stack.reserve(array->size());
|
||||
std::move(array->begin(), array->end(), std::back_inserter(stack));
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.reserve(object->size());
|
||||
for (auto&& it : *object)
|
||||
{
|
||||
free_container(cur);
|
||||
return; // back at the top with nothing left to do
|
||||
stack.push_back(std::move(it.second));
|
||||
}
|
||||
}
|
||||
|
||||
while (!stack.empty())
|
||||
{
|
||||
// move the last item to a local variable to be processed
|
||||
basic_json current_item(std::move(stack.back()));
|
||||
stack.pop_back();
|
||||
|
||||
// if current_item is array/object, move
|
||||
// its children to the stack to be processed later
|
||||
if (current_item.is_array())
|
||||
{
|
||||
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));
|
||||
|
||||
current_item.m_data.m_value.array->clear();
|
||||
}
|
||||
else if (current_item.is_object())
|
||||
{
|
||||
for (auto&& it : *current_item.m_data.m_value.object)
|
||||
{
|
||||
stack.push_back(std::move(it.second));
|
||||
}
|
||||
|
||||
current_item.m_data.m_value.object->clear();
|
||||
}
|
||||
|
||||
// ascend: detach the grandparent link from prev's
|
||||
// last slot, drop that (now null) slot, free cur
|
||||
// (it is empty), then move up one level
|
||||
basic_json gp;
|
||||
take(gp, last_child(prev));
|
||||
pop_last_child(prev);
|
||||
|
||||
free_container(cur);
|
||||
|
||||
take(cur, prev);
|
||||
take(prev, gp);
|
||||
continue;
|
||||
// it's now safe that current_item gets destructed
|
||||
// since it doesn't have any children
|
||||
}
|
||||
|
||||
basic_json& cur_last_ref = last_child(cur);
|
||||
|
||||
if (has_no_children(cur_last_ref))
|
||||
{
|
||||
// scalar, or already-empty array/object
|
||||
pop_last_child(cur);
|
||||
continue;
|
||||
}
|
||||
|
||||
// descend into the non-empty last child, reversing the
|
||||
// link: its slot takes over prev, and the child becomes
|
||||
// the new cur
|
||||
basic_json tmp;
|
||||
take(tmp, cur_last_ref);
|
||||
take(cur_last_ref, prev);
|
||||
take(prev, cur);
|
||||
take(cur, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void destroy(value_t t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case value_t::string:
|
||||
destroy_string();
|
||||
case value_t::object:
|
||||
{
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
AllocatorType<string_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::binary:
|
||||
destroy_binary();
|
||||
break;
|
||||
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
destroy_container(t);
|
||||
{
|
||||
AllocatorType<binary_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::boolean:
|
||||
@@ -825,7 +715,9 @@ public:
|
||||
case value_t::number_float:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -3601,13 +3493,9 @@ public:
|
||||
&& !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
|
||||
ValueType value(const ::nlohmann::json_pointer<BasicJsonType>& ptr, const ValueType& default_value) const
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return value(ptr.convert(), default_value);
|
||||
}
|
||||
#endif
|
||||
|
||||
template < class ValueType, class BasicJsonType, class ReturnType = typename value_return_type<ValueType>::type,
|
||||
detail::enable_if_t <
|
||||
@@ -3616,13 +3504,9 @@ public:
|
||||
&& !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
|
||||
ReturnType value(const ::nlohmann::json_pointer<BasicJsonType>& ptr, ValueType && default_value) const
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return value(ptr.convert(), std::forward<ValueType>(default_value));
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief access the first element
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/front/
|
||||
@@ -3980,13 +3864,9 @@ public:
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
|
||||
bool contains(const typename ::nlohmann::json_pointer<BasicJsonType>& ptr) const
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return ptr.contains(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @}
|
||||
|
||||
@@ -4097,13 +3977,9 @@ public:
|
||||
/// that is, replace `json::iterator_wrapper(j)` with `j.items()`.
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items())
|
||||
static iteration_proxy<iterator> iterator_wrapper(reference ref) noexcept
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return ref.items();
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief wrapper to access iterator member functions in range-based for
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/items/
|
||||
@@ -4112,13 +3988,9 @@ public:
|
||||
/// that is, replace `json::iterator_wrapper(j)` with `j.items()`.
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items())
|
||||
static iteration_proxy<const_iterator> iterator_wrapper(const_reference ref) noexcept
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return ref.items();
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief helper to access iterator member functions in range-based for
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/items/
|
||||
@@ -5207,7 +5079,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_le/
|
||||
template<typename ScalarType>
|
||||
requires std::is_scalar_v<ScalarType>
|
||||
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) <= rhs;
|
||||
}
|
||||
@@ -5216,7 +5088,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_ge/
|
||||
template<typename ScalarType>
|
||||
requires std::is_scalar_v<ScalarType>
|
||||
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) >= rhs;
|
||||
}
|
||||
@@ -5237,16 +5109,11 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
// The friend comparisons with a scalar name the JSON type via decltype of
|
||||
// their parameter in noexcept, because older MSVC versions do not see the
|
||||
// class scope there: MSVC 2015 and 2017 take basic_json as the template,
|
||||
// and MSVC 2019 16.0 rejects member types and template parameters.
|
||||
|
||||
/// @brief comparison: equal
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_eq/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
{
|
||||
return lhs == basic_json(rhs);
|
||||
}
|
||||
@@ -5255,7 +5122,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_eq/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) == rhs;
|
||||
}
|
||||
@@ -5271,7 +5138,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_ne/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
{
|
||||
return lhs != basic_json(rhs);
|
||||
}
|
||||
@@ -5280,7 +5147,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_ne/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) != rhs;
|
||||
}
|
||||
@@ -5300,7 +5167,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
{
|
||||
return lhs < basic_json(rhs);
|
||||
}
|
||||
@@ -5309,7 +5176,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) < rhs;
|
||||
}
|
||||
@@ -5329,7 +5196,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_le/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
{
|
||||
return lhs <= basic_json(rhs);
|
||||
}
|
||||
@@ -5338,7 +5205,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_le/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) <= rhs;
|
||||
}
|
||||
@@ -5359,7 +5226,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_gt/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
{
|
||||
return lhs > basic_json(rhs);
|
||||
}
|
||||
@@ -5368,7 +5235,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_gt/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) > rhs;
|
||||
}
|
||||
@@ -5388,7 +5255,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_ge/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
|
||||
friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
{
|
||||
return lhs >= basic_json(rhs);
|
||||
}
|
||||
@@ -5397,7 +5264,7 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator_ge/
|
||||
template<typename ScalarType, typename std::enable_if<
|
||||
std::is_scalar<ScalarType>::value, int>::type = 0>
|
||||
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
|
||||
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
|
||||
{
|
||||
return basic_json(lhs) >= rhs;
|
||||
}
|
||||
@@ -5433,9 +5300,6 @@ public:
|
||||
return o;
|
||||
}
|
||||
|
||||
#if !JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
// the deleted version is a function template after the class, because
|
||||
// GCC < 5 and Clang < 10 reject deleted friend functions in class templates
|
||||
/// @brief serialize to stream
|
||||
/// @sa https://json.nlohmann.me/api/operator_ltlt/
|
||||
/// @deprecated This function is deprecated since 3.0.0 and will be removed in
|
||||
@@ -5447,7 +5311,6 @@ public:
|
||||
{
|
||||
return o << j;
|
||||
}
|
||||
#endif
|
||||
#endif // JSON_NO_IO
|
||||
/// @}
|
||||
|
||||
@@ -5499,16 +5362,12 @@ public:
|
||||
const bool allow_exceptions = true,
|
||||
const bool ignore_comments = false,
|
||||
const bool ignore_trailing_commas = false)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
basic_json result;
|
||||
auto p = parser(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
||||
p.parse(true, result);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief check if the input is valid JSON
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/accept/
|
||||
@@ -5538,13 +5397,9 @@ public:
|
||||
static bool accept(detail::span_input_adapter&& i,
|
||||
const bool ignore_comments = false,
|
||||
const bool ignore_trailing_commas = false)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief generate SAX events
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/sax_parse/
|
||||
@@ -5605,9 +5460,6 @@ public:
|
||||
const bool strict = true,
|
||||
const bool ignore_comments = false,
|
||||
const bool ignore_trailing_commas = false)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
auto ia = i.get();
|
||||
return format == input_format_t::json
|
||||
@@ -5616,14 +5468,10 @@ public:
|
||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict);
|
||||
}
|
||||
#endif
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
#ifndef JSON_NO_IO
|
||||
#if !JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
// the deleted version is a function template after the class, because
|
||||
// GCC < 5 and Clang < 10 reject deleted friend functions in class templates
|
||||
/// @brief deserialize from stream
|
||||
/// @sa https://json.nlohmann.me/api/operator_gtgt/
|
||||
/// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in
|
||||
@@ -5635,7 +5483,6 @@ public:
|
||||
{
|
||||
return operator>>(i, j);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief deserialize from stream
|
||||
/// @sa https://json.nlohmann.me/api/operator_gtgt/
|
||||
@@ -5961,13 +5808,9 @@ public:
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true,
|
||||
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler);
|
||||
}
|
||||
#endif
|
||||
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len))
|
||||
@@ -5975,13 +5818,9 @@ public:
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true,
|
||||
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return from_binary_impl(i.get(), input_format_t::cbor, strict, allow_exceptions, error_handler_t::keep, tag_handler);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief create a JSON value from an input in MessagePack format
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/
|
||||
@@ -6014,26 +5853,18 @@ public:
|
||||
static basic_json from_msgpack(const T* ptr, std::size_t len,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return from_msgpack(ptr, ptr + len, strict, allow_exceptions);
|
||||
}
|
||||
#endif
|
||||
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len))
|
||||
static basic_json from_msgpack(detail::span_input_adapter&& i,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return from_binary_impl(i.get(), input_format_t::msgpack, strict, allow_exceptions);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief create a JSON value from an input in UBJSON format
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/
|
||||
@@ -6066,26 +5897,18 @@ public:
|
||||
static basic_json from_ubjson(const T* ptr, std::size_t len,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return from_ubjson(ptr, ptr + len, strict, allow_exceptions);
|
||||
}
|
||||
#endif
|
||||
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len))
|
||||
static basic_json from_ubjson(detail::span_input_adapter&& i,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return from_binary_impl(i.get(), input_format_t::ubjson, strict, allow_exceptions);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief create a JSON value from an input in BJData format
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/
|
||||
@@ -6112,20 +5935,6 @@ public:
|
||||
return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bjdata, strict, allow_exceptions, error_handler);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.13.0, from_bjdata(ptr, ptr + len))
|
||||
static basic_json from_bjdata(const T* ptr, std::size_t len,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return from_bjdata(ptr, ptr + len, strict, allow_exceptions);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief create a JSON value from an input in BON8 format
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/from_bon8/
|
||||
template<typename InputType>
|
||||
@@ -6149,20 +5958,6 @@ public:
|
||||
return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bon8, strict, allow_exceptions);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.13.0, from_bon8(ptr, ptr + len))
|
||||
static basic_json from_bon8(const T* ptr, std::size_t len,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return from_bon8(ptr, ptr + len, strict, allow_exceptions);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief create a JSON value from an input in BSON format
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/
|
||||
template<typename InputType>
|
||||
@@ -6194,26 +5989,18 @@ public:
|
||||
static basic_json from_bson(const T* ptr, std::size_t len,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return from_bson(ptr, ptr + len, strict, allow_exceptions);
|
||||
}
|
||||
#endif
|
||||
|
||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len))
|
||||
static basic_json from_bson(detail::span_input_adapter&& i,
|
||||
const bool strict = true,
|
||||
const bool allow_exceptions = true)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return from_binary_impl(i.get(), input_format_t::bson, strict, allow_exceptions);
|
||||
}
|
||||
#endif
|
||||
/// @}
|
||||
|
||||
//////////////////////////
|
||||
@@ -6233,13 +6020,9 @@ public:
|
||||
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
|
||||
reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return ptr.get_unchecked(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief access specified element via JSON Pointer
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/
|
||||
@@ -6251,13 +6034,9 @@ public:
|
||||
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
|
||||
const_reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr) const
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return ptr.get_unchecked(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief access specified element via JSON Pointer
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/at/
|
||||
@@ -6269,13 +6048,9 @@ public:
|
||||
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
|
||||
reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr)
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return ptr.get_checked(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief access specified element via JSON Pointer
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/at/
|
||||
@@ -6287,13 +6062,9 @@ public:
|
||||
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
|
||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
|
||||
const_reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr) const
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return ptr.get_checked(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @brief return flattened JSON value
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/flatten/
|
||||
@@ -7270,18 +7041,6 @@ std::string format_as(const NLOHMANN_BASIC_JSON_TPL& j)
|
||||
return j.dump();
|
||||
}
|
||||
|
||||
#if JSON_DELETE_DEPRECATED_FUNCTIONS && !defined(JSON_NO_IO)
|
||||
/// @brief serialize to stream (deleted; use operator<<(std::ostream&, const basic_json&))
|
||||
/// @sa https://json.nlohmann.me/api/operator_ltlt/
|
||||
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
||||
std::ostream& operator>>(const NLOHMANN_BASIC_JSON_TPL& j, std::ostream& o) = delete;
|
||||
|
||||
/// @brief deserialize from stream (deleted; use operator>>(std::istream&, basic_json&))
|
||||
/// @sa https://json.nlohmann.me/api/operator_gtgt/
|
||||
NLOHMANN_BASIC_JSON_TPL_DECLARATION
|
||||
std::istream& operator<<(NLOHMANN_BASIC_JSON_TPL& j, std::istream& i) = delete;
|
||||
#endif
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
///////////////////////
|
||||
|
||||
+276
-27
@@ -25,7 +25,7 @@
|
||||
#define INCLUDE_NLOHMANN_JSON_VIEW_HPP_
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <cstring> // memcpy, strlen
|
||||
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
||||
#include <map> // map
|
||||
@@ -50,7 +50,10 @@
|
||||
#include <nlohmann/detail/view/builder.hpp>
|
||||
#include <nlohmann/detail/view/compare.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/edit.hpp>
|
||||
#include <nlohmann/detail/view/edit_storage.hpp>
|
||||
#include <nlohmann/detail/view/errors.hpp>
|
||||
#include <nlohmann/detail/view/image.hpp>
|
||||
#include <nlohmann/detail/view/input.hpp>
|
||||
#include <nlohmann/detail/view/iterator.hpp>
|
||||
#include <nlohmann/detail/view/lookup.hpp>
|
||||
@@ -65,7 +68,7 @@
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
template<typename BasicJsonType>
|
||||
template<typename BasicJsonType, bool Editable>
|
||||
class basic_json_document;
|
||||
|
||||
/*!
|
||||
@@ -74,11 +77,13 @@ class basic_json_document;
|
||||
Trivially copyable (two pointers). Valid as long as the document is alive and
|
||||
has not been re-parsed, and as long as a borrowed source text is alive.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
template<typename BasicJsonType, bool Editable = false>
|
||||
class basic_json_view
|
||||
{
|
||||
using node = detail::view::node;
|
||||
using document_data = detail::view::document_data;
|
||||
/// how the index is walked (with edits only for editable documents)
|
||||
using navigation = detail::view::navigation<Editable>;
|
||||
|
||||
public:
|
||||
using value_t = detail::value_t;
|
||||
@@ -271,7 +276,7 @@ class basic_json_view
|
||||
{
|
||||
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
||||
}
|
||||
return idx < m_node->len ? basic_json_view(m_doc, detail::view::element_at(m_node, idx)) : basic_json_view();
|
||||
return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
|
||||
}
|
||||
|
||||
/// (an int argument would be ambiguous between size_type and const char*)
|
||||
@@ -327,7 +332,7 @@ class basic_json_view
|
||||
{
|
||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
return basic_json_view(m_doc, detail::view::element_at(m_node, idx));
|
||||
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
||||
}
|
||||
|
||||
basic_json_view at(int idx) const
|
||||
@@ -399,7 +404,7 @@ class basic_json_view
|
||||
{
|
||||
if (is_structured() && m_node->len != 0)
|
||||
{
|
||||
return basic_json_view(m_doc, detail::view::last_child(m_node) + (is_object() ? 1 : 0));
|
||||
return basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
|
||||
}
|
||||
return front();
|
||||
}
|
||||
@@ -416,7 +421,7 @@ class basic_json_view
|
||||
{
|
||||
return end();
|
||||
}
|
||||
const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size());
|
||||
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
||||
return k != nullptr ? iterator(m_doc, k, true) : end();
|
||||
}
|
||||
|
||||
@@ -433,7 +438,7 @@ class basic_json_view
|
||||
/// whether this is an object with a member with this key
|
||||
bool contains(string_view_t key) const
|
||||
{
|
||||
return is_object() && detail::view::find_member(*m_doc, m_node, key.data(), key.size()) != nullptr;
|
||||
return is_object() && detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr;
|
||||
}
|
||||
|
||||
bool contains(const char* key) const
|
||||
@@ -480,7 +485,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_LIKELY(is_structured()))
|
||||
{
|
||||
return iterator(m_doc, document_data::first_child(m_node), is_object());
|
||||
return iterator(m_doc, navigation::first(*m_doc, m_node), is_object());
|
||||
}
|
||||
return iterator(m_doc, m_node, false);
|
||||
}
|
||||
@@ -489,7 +494,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_LIKELY(is_structured()))
|
||||
{
|
||||
return iterator(m_doc, document_data::child_end(m_node), is_object());
|
||||
return iterator(m_doc, navigation::end(*m_doc, m_node), is_object());
|
||||
}
|
||||
return iterator(m_doc, (is_null() || is_discarded()) ? m_node : m_node + 1, false);
|
||||
}
|
||||
@@ -587,9 +592,11 @@ class basic_json_view
|
||||
style.indent_char = indent_char;
|
||||
style.ensure_ascii = ensure_ascii;
|
||||
style.source_numbers = numbers == number_format::source;
|
||||
// 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;
|
||||
detail::view::view_serializer<BasicJsonType>(*m_doc, out, estimate, style).dump(m_node);
|
||||
// the compact text is about as long as the source text of the value;
|
||||
// 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);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -623,6 +630,19 @@ class basic_json_view
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
/// a view of an editable document compares with one of a read-only document
|
||||
template < bool E, typename std::enable_if < E != Editable, int >::type = 0 >
|
||||
friend bool operator==(const basic_json_view& a, const basic_json_view<BasicJsonType, E>& b)
|
||||
{
|
||||
return detail::view::equal<BasicJsonType>(side(a), detail::view::view_side<BasicJsonType, basic_json_view<BasicJsonType, E>>(b));
|
||||
}
|
||||
|
||||
template < bool E, typename std::enable_if < E != Editable, int >::type = 0 >
|
||||
friend bool operator!=(const basic_json_view& a, const basic_json_view<BasicJsonType, E>& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
/// whether the value parse() would produce for a view equals a value
|
||||
friend bool operator==(const basic_json_view& a, const BasicJsonType& j)
|
||||
{
|
||||
@@ -656,7 +676,7 @@ class basic_json_view
|
||||
{
|
||||
return BasicJsonType(value_t::discarded);
|
||||
}
|
||||
return detail::view::materialize<BasicJsonType>(*m_doc, m_node);
|
||||
return detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
|
||||
}
|
||||
|
||||
/// byte offset of this value in the source text (for strings: of the
|
||||
@@ -664,12 +684,14 @@ class basic_json_view
|
||||
/// a discarded view and for strings with escapes, which are decoded
|
||||
std::size_t source_offset() const noexcept
|
||||
{
|
||||
return m_node != nullptr && (m_node->flags & detail::view::node_flags::storage) == 0
|
||||
return m_node != nullptr && (m_node->flags & (detail::view::node_flags::storage | detail::view::node_flags::moved | detail::view::node_flags::is_new)) == 0
|
||||
? m_node->off : static_cast<std::size_t>(-1);
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename> friend class basic_json_document;
|
||||
template<typename, bool> friend class basic_json_document;
|
||||
template<typename, bool> friend class basic_json_view;
|
||||
template<typename, typename> friend class detail::view::editor;
|
||||
friend iterator;
|
||||
|
||||
basic_json_view(const document_data* d, const node* n) noexcept
|
||||
@@ -687,6 +709,11 @@ class basic_json_view
|
||||
/// decoded strings)
|
||||
std::size_t source_extent() const noexcept
|
||||
{
|
||||
if (Editable && m_doc->edits != nullptr)
|
||||
{
|
||||
// positions of moved and new values are not source offsets
|
||||
return m_node == m_doc->tape ? m_doc->size + m_doc->edits->text_used : 64;
|
||||
}
|
||||
const node* const next = document_data::after(m_node);
|
||||
const bool in_source = (m_node->flags & detail::view::node_flags::storage) == 0;
|
||||
if (!in_source)
|
||||
@@ -704,8 +731,8 @@ class basic_json_view
|
||||
/// (object required)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
|
||||
{
|
||||
const node* const k = detail::view::find_member(*m_doc, m_node, key.data(), key.size());
|
||||
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
|
||||
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
||||
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
|
||||
}
|
||||
|
||||
// --- get() dispatch ---
|
||||
@@ -796,7 +823,7 @@ Borrowed parses keep a pointer to the caller's text, which must outlive the
|
||||
document. Owned parses (parse_copy, rvalue std::string, streams, and inputs
|
||||
that are not contiguous byte ranges) keep their own copy.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
template<typename BasicJsonType, bool Editable = false>
|
||||
class basic_json_document
|
||||
{
|
||||
using document_data = detail::view::document_data;
|
||||
@@ -805,7 +832,7 @@ class basic_json_document
|
||||
"json_view supports 64-bit integer types only");
|
||||
|
||||
public:
|
||||
using view_type = basic_json_view<BasicJsonType>;
|
||||
using view_type = basic_json_view<BasicJsonType, Editable>;
|
||||
using value_t = detail::value_t;
|
||||
|
||||
/// an empty (discarded) document
|
||||
@@ -910,7 +937,7 @@ class basic_json_document
|
||||
/// whether the document holds its own copy of the text
|
||||
bool owns_source() const noexcept
|
||||
{
|
||||
return m_data && !m_data->owned.empty() && m_data->src == m_data->owned.data();
|
||||
return m_data && ((!m_data->owned.empty() && m_data->src == m_data->owned.data()) || !m_data->owned_image.empty());
|
||||
}
|
||||
|
||||
/// number of index nodes (values plus object keys)
|
||||
@@ -919,7 +946,7 @@ class basic_json_document
|
||||
return m_data ? m_data->tape_size : 0;
|
||||
}
|
||||
|
||||
/// bytes held by the document (index, decoded strings, owned text)
|
||||
/// bytes held by the document (index, decoded strings, owned text or image)
|
||||
std::size_t memory_usage() const noexcept
|
||||
{
|
||||
if (!m_data)
|
||||
@@ -928,9 +955,10 @@ class basic_json_document
|
||||
}
|
||||
return sizeof(document_data) + (m_data->inline_cap * sizeof(detail::view::node))
|
||||
+ (m_data->tape != m_data->inline_tape ? m_data->tape_cap * sizeof(detail::view::node) : 0)
|
||||
+ m_data->arena.capacity() + m_data->owned.capacity()
|
||||
+ m_data->arena.capacity() + m_data->owned.capacity() + m_data->owned_image.capacity()
|
||||
+ (m_data->indexes.capacity() * sizeof(document_data::object_index)) + (m_data->index_slots.capacity() * sizeof(std::uint32_t))
|
||||
+ (m_data->large_objects.capacity() * sizeof(std::uint32_t));
|
||||
+ (m_data->large_objects.capacity() * sizeof(std::uint32_t))
|
||||
+ (m_data->edits != nullptr ? m_data->edits->bytes : 0);
|
||||
}
|
||||
|
||||
/// release unused capacity of the index and the decoded strings; like
|
||||
@@ -947,9 +975,12 @@ class basic_json_document
|
||||
|
||||
// allocate everything first, so that an exception leaves the document
|
||||
// unchanged
|
||||
// (the decoded strings of a loaded image stay in the image)
|
||||
const bool arena_in_use = d.base[1] == d.arena.data();
|
||||
const bool shrink_arena = d.arena.capacity() > d.arena.size();
|
||||
std::string arena(shrink_arena ? d.arena : std::string());
|
||||
const bool shrink_tape = d.tape != d.inline_tape && d.tape_size != d.tape_cap;
|
||||
std::string arena(shrink_arena && arena_in_use ? d.arena : std::string());
|
||||
// (edits link to the nodes of the index, which then stays in place)
|
||||
const bool shrink_tape = d.tape != d.inline_tape && d.tape_size != d.tape_cap && d.edits == nullptr;
|
||||
const bool into_header = d.tape_size <= d.inline_cap;
|
||||
node* fresh = (shrink_tape && !into_header) ? static_cast<node*>(::operator new (d.tape_size * sizeof(node))) : d.inline_tape;
|
||||
|
||||
@@ -963,13 +994,219 @@ class basic_json_document
|
||||
if (shrink_arena)
|
||||
{
|
||||
d.arena.swap(arena);
|
||||
d.base[1] = d.arena.data();
|
||||
if (arena_in_use)
|
||||
{
|
||||
d.base[1] = d.arena.data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////
|
||||
// images //
|
||||
////////////
|
||||
|
||||
/// how load() checks an image (full, bounds, or none)
|
||||
using image_check = detail::view::image_check;
|
||||
|
||||
/// The document as an image that load() reads without parsing: the node
|
||||
/// index, the text, and the decoded strings. An edited document is
|
||||
/// written in its current state (floats that are not finite become null,
|
||||
/// as in dump()).
|
||||
std::vector<std::uint8_t> save() const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
||||
{
|
||||
detail::view::throw_type_error(320, "cannot save a discarded json_document");
|
||||
}
|
||||
return detail::view::save_image(*m_data);
|
||||
}
|
||||
|
||||
/// Read an image written by save(). The image is borrowed: it must stay
|
||||
/// alive and unchanged while the document is used.
|
||||
NLOHMANN_VIEW_NODISCARD
|
||||
static basic_json_document load(const std::uint8_t* image, std::size_t size, const image_check check = image_check::full)
|
||||
{
|
||||
basic_json_document d;
|
||||
d.ensure_data(nullptr, 0);
|
||||
detail::view::load_image(*d.m_data, image, size, check);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// read an image (borrowed)
|
||||
NLOHMANN_VIEW_NODISCARD
|
||||
static basic_json_document load(const std::vector<std::uint8_t>& image, const image_check check = image_check::full)
|
||||
{
|
||||
return load(image.data(), image.size(), check);
|
||||
}
|
||||
|
||||
/// read an image and keep it (no copy)
|
||||
NLOHMANN_VIEW_NODISCARD
|
||||
static basic_json_document load(std::vector<std::uint8_t>&& image, const image_check check = image_check::full)
|
||||
{
|
||||
basic_json_document d;
|
||||
d.ensure_data(nullptr, 0);
|
||||
d.m_data->owned_image = std::move(image);
|
||||
detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check);
|
||||
return d;
|
||||
}
|
||||
|
||||
///////////
|
||||
// edits //
|
||||
///////////
|
||||
|
||||
// The source text is never written; new values go to storage owned by
|
||||
// the document. A view keeps referring to the same value: after an
|
||||
// assignment it sees the new value, and edits elsewhere do not affect it.
|
||||
// A view of an erased value keeps its last value. An edit of an
|
||||
// array/object invalidates the iterators over it. Values are accepted as
|
||||
// views (of any document), BasicJsonType values, and everything
|
||||
// BasicJsonType can be constructed from.
|
||||
|
||||
using string_view_t = typename view_type::string_view_t;
|
||||
using json_pointer = typename BasicJsonType::json_pointer;
|
||||
|
||||
/// replace a value (a view of this document); returns a view of it
|
||||
template<typename V>
|
||||
view_type set(view_type target, V&& value)
|
||||
{
|
||||
return editor().set(target, std::forward<V>(value));
|
||||
}
|
||||
|
||||
/// set a member (added if missing; a null value becomes an object);
|
||||
/// returns a view of the member value
|
||||
template<typename V>
|
||||
view_type set(view_type object, string_view_t key, V&& value)
|
||||
{
|
||||
return editor().set(object, key, std::forward<V>(value));
|
||||
}
|
||||
|
||||
/// assign an existing array element; returns a view of it
|
||||
template < typename I, typename V, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 >
|
||||
view_type set(view_type array, I idx, V && value)
|
||||
{
|
||||
return editor().set(array, index(idx), std::forward<V>(value));
|
||||
}
|
||||
|
||||
/// set the value at a JSON pointer: its parent must exist; an object
|
||||
/// member is set (added if missing), an array element assigned, and "-"
|
||||
/// or the size of the array appends
|
||||
template<typename V>
|
||||
view_type set(const json_pointer& ptr, V&& value)
|
||||
{
|
||||
if (ptr.empty())
|
||||
{
|
||||
return set(root(), std::forward<V>(value));
|
||||
}
|
||||
const view_type parent = root().at(ptr.parent_pointer());
|
||||
const auto& token = ptr.back();
|
||||
if (parent.is_array())
|
||||
{
|
||||
const std::size_t idx = token == "-" ? parent.size() : pointer_index(token);
|
||||
if (idx == parent.size())
|
||||
{
|
||||
return push_back(parent, std::forward<V>(value));
|
||||
}
|
||||
return set(parent, idx, std::forward<V>(value));
|
||||
}
|
||||
return set(parent, string_view_t(token.data(), token.size()), std::forward<V>(value));
|
||||
}
|
||||
|
||||
/// append to an array (a null value becomes an array); returns a view of
|
||||
/// the new element
|
||||
template<typename V>
|
||||
view_type push_back(view_type array, V&& value)
|
||||
{
|
||||
return editor().push_back(array, std::forward<V>(value));
|
||||
}
|
||||
|
||||
/// insert into an array before position idx (idx <= size()); returns a
|
||||
/// view of the new element
|
||||
template < typename I, typename V, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 >
|
||||
view_type insert(view_type array, I idx, V && value)
|
||||
{
|
||||
return editor().insert(array, index(idx), std::forward<V>(value));
|
||||
}
|
||||
|
||||
/// remove all members with this key; returns their number
|
||||
std::size_t erase(view_type object, string_view_t key)
|
||||
{
|
||||
return editor().erase(object, key);
|
||||
}
|
||||
|
||||
/// remove an array element
|
||||
template < typename I, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 >
|
||||
void erase(view_type array, I idx)
|
||||
{
|
||||
editor().erase(array, index(idx));
|
||||
}
|
||||
|
||||
/// remove the value at a JSON pointer; returns the number of removed
|
||||
/// values
|
||||
std::size_t erase(const json_pointer& ptr)
|
||||
{
|
||||
if (ptr.empty())
|
||||
{
|
||||
detail::view::throw_out_of_range(405, "JSON pointer has no parent");
|
||||
}
|
||||
const view_type parent = root().at(ptr.parent_pointer());
|
||||
const auto& token = ptr.back();
|
||||
if (parent.is_array())
|
||||
{
|
||||
erase(parent, pointer_index(token));
|
||||
return 1;
|
||||
}
|
||||
return erase(parent, string_view_t(token.data(), token.size()));
|
||||
}
|
||||
|
||||
private:
|
||||
using input_kind = detail::view::input_kind;
|
||||
|
||||
detail::view::editor<BasicJsonType, view_type> editor()
|
||||
{
|
||||
static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)");
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
||||
{
|
||||
detail::view::throw_invalid_iterator(202, "view does not belong to this document");
|
||||
}
|
||||
return detail::view::editor<BasicJsonType, view_type>(*m_data);
|
||||
}
|
||||
|
||||
/// an index (out_of_range.401 if negative)
|
||||
template<typename I>
|
||||
static std::size_t index(I idx)
|
||||
{
|
||||
return index(idx, std::is_signed<I> {});
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
static std::size_t index(I idx, std::true_type /*signed*/)
|
||||
{
|
||||
if (idx < 0)
|
||||
{
|
||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
return static_cast<std::size_t>(idx);
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
static std::size_t index(I idx, std::false_type /*unsigned*/)
|
||||
{
|
||||
return static_cast<std::size_t>(idx);
|
||||
}
|
||||
|
||||
/// the array index of a JSON pointer token (json_pointer's rules)
|
||||
template<typename StringType>
|
||||
static std::size_t pointer_index(const StringType& token)
|
||||
{
|
||||
std::size_t idx = 0;
|
||||
const detail::view::index_status status = detail::view::array_index(token, idx);
|
||||
if (status != detail::view::index_status::ok)
|
||||
{
|
||||
detail::view::throw_array_index_error(status, token);
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
/// create the storage (sized for the input) on first use
|
||||
void ensure_data(const char* src, std::size_t size)
|
||||
{
|
||||
@@ -996,9 +1233,12 @@ class basic_json_document
|
||||
{
|
||||
d.owned.clear();
|
||||
}
|
||||
d.owned_image.clear();
|
||||
d.src = src;
|
||||
d.size = size;
|
||||
d.tape_size = 0;
|
||||
d.edits.reset(); // (views of the previous text end here anyway)
|
||||
d.base[2] = nullptr;
|
||||
d.arena.clear();
|
||||
d.indexes.clear();
|
||||
d.index_slots.clear();
|
||||
@@ -1018,6 +1258,7 @@ class basic_json_document
|
||||
{
|
||||
d.base[0] = d.src;
|
||||
d.base[1] = d.arena.data();
|
||||
d.arena_size = d.arena.size();
|
||||
detail::view::build_object_indexes(d);
|
||||
d.discarded = false;
|
||||
return;
|
||||
@@ -1136,6 +1377,14 @@ using json_view = basic_json_view<json>;
|
||||
using ordered_json_document = basic_json_document<ordered_json>;
|
||||
/// a value of an ordered_json_document
|
||||
using ordered_json_view = basic_json_view<ordered_json>;
|
||||
/// an editable parsed JSON text for json
|
||||
using json_editable_document = basic_json_document<json, true>;
|
||||
/// a value of a json_editable_document
|
||||
using json_editable_view = basic_json_view<json, true>;
|
||||
/// an editable parsed JSON text for ordered_json
|
||||
using ordered_json_editable_document = basic_json_document<ordered_json, true>;
|
||||
/// a value of an ordered_json_editable_document
|
||||
using ordered_json_editable_view = basic_json_view<ordered_json, true>;
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+317
-850
File diff suppressed because it is too large.
Load diff
@@ -67,10 +67,6 @@
|
||||
#define JSON_STRICT_BINARY_UTF8 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||
#else
|
||||
@@ -113,20 +109,14 @@
|
||||
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
|
||||
#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
|
||||
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||
#endif
|
||||
|
||||
// Construct the namespace ABI tags component
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h) json_abi ## a ## b ## c ## d ## e ## f ## g ## h
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(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) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g)
|
||||
|
||||
#define NLOHMANN_JSON_ABI_TAGS \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||
@@ -136,8 +126,7 @@
|
||||
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
|
||||
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
|
||||
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS)
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8)
|
||||
|
||||
// Construct the namespace version component
|
||||
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||
|
||||
Loaded 100 of 138 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user