mirror of
https://github.com/nlohmann/json.git
synced 2026-11-03 11:25:33 +00:00
Compare commits
41
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f64464daf1 | ||
|
|
baba5db36f | ||
|
|
07876e2292 | ||
|
|
b18b6fc7e6 | ||
|
|
6d5581c4e8 | ||
|
|
590b622d48 | ||
|
|
100f676b3e | ||
|
|
bd067fd08c | ||
|
|
3354436ec4 | ||
|
|
596028c460 | ||
|
|
5c4f585ac2 | ||
|
|
1d69ef9ab4 | ||
|
|
ff0bdfdef3 | ||
|
|
7328310136 | ||
|
|
bfe69c73c2 | ||
|
|
494a74c242 | ||
|
|
bdc737db6d | ||
|
|
66e2eeeec0 | ||
|
|
03d408aafa | ||
|
|
76dbe8f9c9 | ||
|
|
b2674a4a2f | ||
|
|
e2423a1857 | ||
|
|
67b103e506 | ||
|
|
ddc34f546a | ||
|
|
f022e3c59c | ||
|
|
9742edc294 | ||
|
|
517444b504 | ||
|
|
fb515ddc34 | ||
|
|
edf8d17601 | ||
|
|
aecbef3949 | ||
|
|
6c3ab966d3 | ||
|
|
5337d841d4 | ||
|
|
162eda2574 | ||
|
|
1a018577cc | ||
|
|
76001c6b4c | ||
|
|
ec6986c918 | ||
|
|
3fd3e0fdaa | ||
|
|
9c349b1d7c | ||
|
|
367963f6bb | ||
|
|
4fe670b5f4 | ||
|
|
755bb16d7a |
No files matched your search
@@ -24,7 +24,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
actions: write
|
actions: write
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
save:
|
save:
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
check_docs_links:
|
check_docs_links:
|
||||||
if: github.repository == 'nlohmann/json'
|
if: github.repository == 'nlohmann/json'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ jobs:
|
|||||||
comment:
|
comment:
|
||||||
# push runs on develop have no PR to comment on (and no "pr" artifact)
|
# push runs on develop have no PR to comment on (and no "pr" artifact)
|
||||||
if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }}
|
if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
dependency-review:
|
dependency-review:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
flawfinder:
|
flawfinder:
|
||||||
name: Flawfinder
|
name: Flawfinder
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
permissions:
|
permissions:
|
||||||
actions: read
|
actions: read
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
if: github.repository == 'nlohmann/json'
|
if: github.repository == 'nlohmann/json'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
analysis:
|
analysis:
|
||||||
name: Scorecard analysis
|
name: Scorecard analysis
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
permissions:
|
permissions:
|
||||||
# Needed to upload the results to code-scanning dashboard.
|
# Needed to upload the results to code-scanning dashboard.
|
||||||
security-events: write
|
security-events: write
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ci_test_gcc:
|
ci_test_gcc:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
container: gcc:latest
|
container: gcc:latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
@@ -130,7 +130,7 @@ jobs:
|
|||||||
run: cmake --build build --target ${{ matrix.target }}
|
run: cmake --build build --target ${{ matrix.target }}
|
||||||
|
|
||||||
ci_static_analysis_clang:
|
ci_static_analysis_clang:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
container: silkeh/clang:22
|
container: silkeh/clang:22
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -149,7 +149,7 @@ jobs:
|
|||||||
run: cmake --build build --target ${{ matrix.target }}
|
run: cmake --build build --target ${{ matrix.target }}
|
||||||
|
|
||||||
ci_cmake_options:
|
ci_cmake_options:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
container: ubuntu:24.04
|
container: ubuntu:24.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -209,7 +209,7 @@ jobs:
|
|||||||
fail-on-error: false
|
fail-on-error: false
|
||||||
|
|
||||||
ci_test_compilers_gcc_old:
|
ci_test_compilers_gcc_old:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
compiler: ['4.8', '4.9', '5', '6']
|
compiler: ['4.8', '4.9', '5', '6']
|
||||||
@@ -251,7 +251,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_test_compiler_g++-${{ matrix.compiler }}
|
run: cmake --build build --target ci_test_compiler_g++-${{ matrix.compiler }}
|
||||||
|
|
||||||
ci_test_compilers_gcc:
|
ci_test_compilers_gcc:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
# older GCC docker images (4, 5, 6) fail to check out code
|
# older GCC docker images (4, 5, 6) fail to check out code
|
||||||
@@ -269,7 +269,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_test_compiler_default
|
run: cmake --build build --target ci_test_compiler_default
|
||||||
|
|
||||||
ci_test_compilers_clang:
|
ci_test_compilers_clang:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', '21', '22', 'latest']
|
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', '21', '22', 'latest']
|
||||||
@@ -289,7 +289,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_test_compiler_default
|
run: cmake --build build --target ci_test_compiler_default
|
||||||
|
|
||||||
ci_test_standards_gcc:
|
ci_test_standards_gcc:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
container: gcc:latest
|
container: gcc:latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -306,7 +306,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_test_gcc_cxx${{ matrix.standard }}
|
run: cmake --build build --target ci_test_gcc_cxx${{ matrix.standard }}
|
||||||
|
|
||||||
ci_test_standards_clang:
|
ci_test_standards_clang:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
container: silkeh/clang:latest
|
container: silkeh/clang:latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -330,7 +330,7 @@ jobs:
|
|||||||
if: ${{ matrix.stdlib == 'libstdcxx' }}
|
if: ${{ matrix.stdlib == 'libstdcxx' }}
|
||||||
|
|
||||||
ci_cuda_example:
|
ci_cuda_example:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -356,7 +356,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
container: ['gcc:latest', 'silkeh/clang:latest']
|
container: ['gcc:latest', 'silkeh/clang:latest']
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
container: ${{ matrix.container }}
|
container: ${{ matrix.container }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
@@ -383,7 +383,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_module_cpp20
|
run: cmake --build build --target ci_module_cpp20
|
||||||
|
|
||||||
ci_icpc:
|
ci_icpc:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
# Intel discontinued the classic icc/icpc compiler in oneAPI 2024.0; this is
|
# Intel discontinued the classic icc/icpc compiler in oneAPI 2024.0; this is
|
||||||
# Intel's own last officially published image that still includes it.
|
# Intel's own last officially published image that still includes it.
|
||||||
container: intel/oneapi-hpckit:2023.2.1-devel-ubuntu22.04
|
container: intel/oneapi-hpckit:2023.2.1-devel-ubuntu22.04
|
||||||
@@ -402,7 +402,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_icpc
|
run: cmake --build build --target ci_icpc
|
||||||
|
|
||||||
ci_icpx:
|
ci_icpx:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
container: intel/oneapi-hpckit:latest
|
container: intel/oneapi-hpckit:latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
@@ -416,7 +416,7 @@ jobs:
|
|||||||
run: cmake --build build --target ci_icpx
|
run: cmake --build build --target ci_icpx
|
||||||
|
|
||||||
ci_nvhpc:
|
ci_nvhpc:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-26.04
|
||||||
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|||||||
+3
-3
@@ -348,20 +348,20 @@ add_custom_target(ci_test_no_thread_local
|
|||||||
|
|
||||||
add_custom_target(ci_test_coverage
|
add_custom_target(ci_test_coverage
|
||||||
COMMAND CXX=g++ ${CMAKE_COMMAND}
|
COMMAND CXX=g++ ${CMAKE_COMMAND}
|
||||||
-DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_CXX_FLAGS="--coverage;-fprofile-arcs;-ftest-coverage"
|
-DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_CXX_FLAGS="--coverage;-fprofile-arcs;-ftest-coverage;-fvar-tracking-assignments"
|
||||||
-DJSON_BuildTests=ON
|
-DJSON_BuildTests=ON
|
||||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_coverage
|
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_coverage
|
||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_coverage
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_coverage
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_coverage && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_coverage && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||||
|
|
||||||
COMMAND CXX=g++ ${CMAKE_COMMAND}
|
COMMAND CXX=g++ ${CMAKE_COMMAND}
|
||||||
-DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_CXX_FLAGS="-m32;--coverage;-fprofile-arcs;-ftest-coverage"
|
-DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_CXX_FLAGS="-m32;--coverage;-fprofile-arcs;-ftest-coverage;-fvar-tracking-assignments"
|
||||||
-DJSON_BuildTests=ON -DJSON_32bitTest=ONLY
|
-DJSON_BuildTests=ON -DJSON_32bitTest=ONLY
|
||||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_coverage32
|
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_coverage32
|
||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_coverage32
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_coverage32
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_coverage32 && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_coverage32 && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||||
|
|
||||||
COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors mismatch --ignore-errors unused --ignore-errors inconsistent
|
COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc branch_coverage=1 --ignore-errors mismatch --ignore-errors unused --ignore-errors inconsistent
|
||||||
COMMAND ${LCOV_TOOL} -e json.info ${SRC_FILES} --output-file json.info.filtered --rc branch_coverage=1 --ignore-errors unused --ignore-errors inconsistent
|
COMMAND ${LCOV_TOOL} -e json.info ${SRC_FILES} --output-file json.info.filtered --rc branch_coverage=1 --ignore-errors unused --ignore-errors inconsistent
|
||||||
COMMAND ${CMAKE_SOURCE_DIR}/tests/thirdparty/imapdl/filterbr.py json.info.filtered > json.info.filtered.noexcept
|
COMMAND ${CMAKE_SOURCE_DIR}/tests/thirdparty/imapdl/filterbr.py json.info.filtered > json.info.filtered.noexcept
|
||||||
COMMAND ${GENHTML_TOOL} --title "JSON for Modern C++" --legend --demangle-cpp --output-directory html --show-details --branch-coverage --ignore-errors inconsistent json.info.filtered.noexcept
|
COMMAND ${GENHTML_TOOL} --title "JSON for Modern C++" --legend --demangle-cpp --output-directory html --show-details --branch-coverage --ignore-errors inconsistent json.info.filtered.noexcept
|
||||||
|
|||||||
+3
-1
@@ -178,8 +178,10 @@ function(_json_test_add_test test_name file main cxx_standard)
|
|||||||
COMMAND ${memcheck_command} $<TARGET_FILE:${test_target}> ${DOCTEST_TEST_FILTER}
|
COMMAND ${memcheck_command} $<TARGET_FILE:${test_target}> ${DOCTEST_TEST_FILTER}
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
)
|
)
|
||||||
|
# under Valgrind the slowest tests take close to 25 minutes, so ctest's
|
||||||
|
# default timeout of 1500 seconds leaves no headroom
|
||||||
set_tests_properties(${test_target}_valgrind PROPERTIES
|
set_tests_properties(${test_target}_valgrind PROPERTIES
|
||||||
LABELS "valgrind" FIXTURES_REQUIRED TEST_DATA
|
LABELS "valgrind" FIXTURES_REQUIRED TEST_DATA TIMEOUT 3600
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|||||||
@@ -120,6 +120,16 @@ overload (3).
|
|||||||
See [Number conversion](../../features/types/number_handling.md#number-conversion)
|
See [Number conversion](../../features/types/number_handling.md#number-conversion)
|
||||||
for more information.
|
for more information.
|
||||||
|
|
||||||
|
!!! note "Booleans as numbers"
|
||||||
|
|
||||||
|
A JSON boolean converts to `#!cpp 0` or `#!cpp 1` for every arithmetic type **except** [`number_integer_t`](number_integer_t.md),
|
||||||
|
[`number_unsigned_t`](number_unsigned_t.md) and [`number_float_t`](number_float_t.md) (by default `#!cpp std::int64_t`,
|
||||||
|
`#!cpp std::uint64_t`, and `#!cpp double`), for which `#!cpp get<T>()` throws
|
||||||
|
[`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) ("type must be number, but is boolean"). So
|
||||||
|
`#!cpp json(true).get<int>()` is `#!cpp 1`, while `#!cpp json(true).get<std::int64_t>()` and
|
||||||
|
`#!cpp json(true).get<double>()` throw. This is the behavior of all 3.x versions;
|
||||||
|
[`basic_json_view::get<T>()`](../basic_json_view/get.md) behaves the same.
|
||||||
|
|
||||||
!!! note "`std::optional` conversions"
|
!!! note "`std::optional` conversions"
|
||||||
|
|
||||||
Prior to version 3.13.0, `#!cpp get<std::optional<T>>()` (and other conversions to `std::optional<T>`) failed to
|
Prior to version 3.13.0, `#!cpp get<std::optional<T>>()` (and other conversions to `std::optional<T>`) failed to
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ Besides `basic_json`, the library offers a second way to hold a parsed JSON text
|
|||||||
[`basic_json_document`](../basic_json_document/index.md), which owns or borrows the text and holds a flat index of the
|
[`basic_json_document`](../basic_json_document/index.md), which owns or borrows the text and holds a flat index of the
|
||||||
values in it, and a [`basic_json_view`](../basic_json_view/index.md), a read-only handle to one value of such a document.
|
values in it, and a [`basic_json_view`](../basic_json_view/index.md), a read-only handle to one value of such a document.
|
||||||
Both are defined in the header `<nlohmann/json_view.hpp>`; see
|
Both are defined in the header `<nlohmann/json_view.hpp>`; see
|
||||||
[Zero-copy JSON views](../../features/json_view.md) for the concept and the trade-offs.
|
[Zero-copy JSON views](../../features/json_view/index.md) for the concept and the trade-offs.
|
||||||
|
|
||||||
- [**json_document**](../json_document.md) / [**ordered_json_document**](../ordered_json_document.md) - read-only
|
- [**json_document**](../json_document.md) / [**ordered_json_document**](../ordered_json_document.md) - read-only
|
||||||
documents of the `basic_json` specialization they belong to
|
documents of the `basic_json` specialization they belong to
|
||||||
@@ -363,7 +363,7 @@ Access to the JSON value
|
|||||||
specification of the JSON data interchange format
|
specification of the JSON data interchange format
|
||||||
- [`basic_json_document`](../basic_json_document/index.md) - / [`basic_json_view`](../basic_json_view/index.md) - parse a JSON
|
- [`basic_json_document`](../basic_json_document/index.md) - / [`basic_json_view`](../basic_json_view/index.md) - parse a JSON
|
||||||
text into a flat index and read it through a view
|
text into a flat index and read it through a view
|
||||||
- [Zero-copy JSON views](../../features/json_view.md) - when to use a document and a view instead of a `basic_json`
|
- [Zero-copy JSON views](../../features/json_view/index.md) - when to use a document and a view instead of a `basic_json`
|
||||||
- [Types](../../features/types/index.md) - how JSON values are stored and which types exist
|
- [Types](../../features/types/index.md) - how JSON values are stored and which types exist
|
||||||
- [Converting values](../../features/conversions.md) - get values out of a JSON value and put values in
|
- [Converting values](../../features/conversions.md) - get values out of a JSON value and put values in
|
||||||
- [Creating JSON values](../../features/creating_values.md) - all the ways to build a JSON value
|
- [Creating JSON values](../../features/creating_values.md) - all the ways to build a JSON value
|
||||||
|
|||||||
@@ -65,7 +65,12 @@ the same index, which is now a different value.
|
|||||||
|
|
||||||
## Exception safety
|
## Exception safety
|
||||||
|
|
||||||
Strong exception safety: if an exception occurs, the original value stays intact.
|
Overload (1) gives the strong guarantee: if an exception is thrown, there are no changes in the JSON value.
|
||||||
|
|
||||||
|
Overloads (2) to (5) give the basic guarantee: if an exception is thrown while the elements or members are copied
|
||||||
|
(for example, `std::bad_alloc`), the JSON value may be partially modified, but it stays a valid array or object.
|
||||||
|
|
||||||
|
The exceptions listed below are thrown before the JSON value is changed.
|
||||||
|
|
||||||
## Exceptions
|
## Exceptions
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
|||||||
[`basic_json_document::parse`](../basic_json_document/parse.md) accepts the same inputs and the same options, but
|
[`basic_json_document::parse`](../basic_json_document/parse.md) accepts the same inputs and the same options, but
|
||||||
keeps the result as a flat index of the parsed values instead of a tree of `basic_json` values. It is faster and uses
|
keeps the result as a flat index of the parsed values instead of a tree of `basic_json` values. It is faster and uses
|
||||||
less memory when only parts of the document are read, and it also accepts a pointer to a null-terminated wide string,
|
less memory when only parts of the document are read, and it also accepts a pointer to a null-terminated wide string,
|
||||||
which `parse()` does not. See [Zero-copy JSON views](../../features/json_view.md).
|
which `parse()` does not. See [Zero-copy JSON views](../../features/json_view/index.md).
|
||||||
|
|
||||||
!!! tip "Performance"
|
!!! tip "Performance"
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,15 @@ function never throws an exception for invalid input, and the returned `#!cpp bo
|
|||||||
is returned.
|
is returned.
|
||||||
|
|
||||||
The result is the same as that of `BasicJsonType::accept()` by design; the difference is how the check is made. This
|
The result is the same as that of `BasicJsonType::accept()` by design; the difference is how the check is made. This
|
||||||
function runs the document's index builder (and discards the index), while `BasicJsonType::accept()` runs the SAX
|
function runs the document's builder, which checks the text as [`parse()`](parse.md) does, while
|
||||||
parser with a handler that ignores every event. Use this one if you go on to parse the same input with
|
`BasicJsonType::accept()` runs the SAX parser with a handler that ignores every event. Use this one if you go on to
|
||||||
[`parse()`](parse.md) anyway, so that both use the same code; either is fine for a plain yes/no answer.
|
parse the same input with [`parse()`](parse.md) anyway, so that both use the same code; either is fine for a plain
|
||||||
|
yes/no answer.
|
||||||
|
|
||||||
|
When both `ignore_comments` and `ignore_trailing_commas` are `#!cpp false` (the default), the builder writes no index.
|
||||||
|
With either option set, it builds the index as [`parse()`](parse.md) does and discards it: the result is the same, only
|
||||||
|
the time differs. (Neither option needs the index; the variant that skips it exists only for the default options to
|
||||||
|
keep the code size down.)
|
||||||
|
|
||||||
## Template parameters
|
## Template parameters
|
||||||
|
|
||||||
@@ -71,7 +77,7 @@ compile; see [`parse`](parse.md#notes).
|
|||||||
|
|
||||||
- [`parse`](parse.md) - deserialize from a compatible input
|
- [`parse`](parse.md) - deserialize from a compatible input
|
||||||
- [`BasicJsonType::accept`](../basic_json/accept.md) - the corresponding function of `basic_json`
|
- [`BasicJsonType::accept`](../basic_json/accept.md) - the corresponding function of `basic_json`
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#what-is-the-same-as-parse) - how parsing a document differs from, and matches, basic_json::parse
|
- [Zero-copy JSON views](../../features/json_view/differences.md#what-is-the-same) - how parsing a document differs from, and matches, basic_json::parse
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ Constant.
|
|||||||
|
|
||||||
- [`parse`](parse.md) - deserialize from a compatible input
|
- [`parse`](parse.md) - deserialize from a compatible input
|
||||||
- [`is_discarded`](is_discarded.md) - return whether the last parse failed
|
- [`is_discarded`](is_discarded.md) - return whether the last parse failed
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#ownership-and-lifetime) - who owns the source text and for how long
|
- [Zero-copy JSON views](../../features/json_view/lifetime.md) - who owns the source text and for how long
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ by an erase, not only those at or after the removed element, since the container
|
|||||||
- [`root`](root.md) - the view of the root value, the starting point of overload 3
|
- [`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`
|
- [`BasicJsonType::erase`](../basic_json/erase.md) - the corresponding function of `basic_json`
|
||||||
- [Edits](index.md#edits) - what an edit guarantees, for every overload
|
- [Edits](index.md#edits) - what an edit guarantees, for every overload
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#editing-a-document) - what an edit guarantees and costs
|
- [Zero-copy JSON views](../../features/json_view/editing.md) - what an edit guarantees and costs
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ points apply to every edit:
|
|||||||
anything `BasicJsonType` can be constructed from (numbers, strings, `#!cpp bool`, `#!cpp nullptr`, containers, ...).
|
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
|
- [`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
|
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
|
number that was not itself edited — see [Editing a document](../../features/json_view/editing.md) for
|
||||||
why this matters.
|
why this matters.
|
||||||
- [`read()`](read.md) discards all edits, [`shrink_to_fit()`](shrink_to_fit.md) does not move the node index once
|
- [`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.
|
there are edits, and [`memory_usage()`](memory_usage.md) includes the memory edits use.
|
||||||
@@ -118,7 +118,7 @@ points apply to every edit:
|
|||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
- [Zero-copy JSON views](../../features/json_view.md) - guide to documents and views
|
- [Zero-copy JSON views](../../features/json_view/index.md) - guide to documents and views
|
||||||
- [`basic_json_view`](../basic_json_view/index.md) - the non-owning handle to a value in a document
|
- [`basic_json_view`](../basic_json_view/index.md) - the non-owning handle to a value in a document
|
||||||
- [`json_document`](../json_document.md) - and [`json_editable_document`](../json_editable_document.md) - the default specializations
|
- [`json_document`](../json_document.md) - and [`json_editable_document`](../json_editable_document.md) - the default specializations
|
||||||
- [`materialize`](../basic_json_view/materialize.md) - build a `basic_json` value from a view
|
- [`materialize`](../basic_json_view/materialize.md) - build a `basic_json` value from a view
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ an edit in general.
|
|||||||
- [`set`](set.md) - replace a value, or set 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`
|
- [`BasicJsonType::insert`](../basic_json/insert.md) - the corresponding function of `basic_json`
|
||||||
- [Edits](index.md#edits) - what an edit guarantees, for every overload
|
- [Edits](index.md#edits) - what an edit guarantees, for every overload
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#editing-a-document) - what an edit guarantees and costs
|
- [Zero-copy JSON views](../../features/json_view/editing.md) - what an edit guarantees and costs
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ When the document is discarded, [`root()`](root.md) returns a discarded view (it
|
|||||||
|
|
||||||
- [`parse`](parse.md) - deserialize from a compatible input
|
- [`parse`](parse.md) - deserialize from a compatible input
|
||||||
- [`basic_json_view::is_discarded`](../basic_json_view/is_discarded.md) - return whether a view is invalid
|
- [`basic_json_view::is_discarded`](../basic_json_view/is_discarded.md) - return whether a view is invalid
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#what-is-the-same-as-parse) - how a discarded document arises
|
- [Zero-copy JSON views](../../features/json_view/differences.md#what-is-the-same) - how a discarded document arises
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ static basic_json_document load(std::vector<std::uint8_t>&& image,
|
|||||||
|
|
||||||
In every overload, the node index is copied into storage the document itself owns — so that it is properly aligned,
|
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
|
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
|
`image`. The hash indexes [large objects](../../features/json_view/index.md) use for lookup are rebuilt, exactly as after
|
||||||
parsing.
|
parsing.
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
@@ -172,7 +172,7 @@ returned document, and any [view](../basic_json_view/index.md) taken from it, is
|
|||||||
- [`save`](save.md) - write the document as an image
|
- [`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
|
- [`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
|
- [`parse`](parse.md) - deserialize from JSON text instead of an image
|
||||||
- [Images](../../features/json_view.md#images) - why and when to use images
|
- [Images](../../features/json_view/images.md) - why and when to use images
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ to observe the effect of [`shrink_to_fit()`](shrink_to_fit.md).
|
|||||||
|
|
||||||
- [`node_count`](node_count.md) - the number of index entries
|
- [`node_count`](node_count.md) - the number of index entries
|
||||||
- [`shrink_to_fit`](shrink_to_fit.md) - release unused index capacity
|
- [`shrink_to_fit`](shrink_to_fit.md) - release unused index capacity
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#the-idea) - the flat node index and its memory
|
- [Zero-copy JSON views](../../features/json_view/index.md#the-idea) - the flat node index and its memory
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ strings and, for an owned document, the text).
|
|||||||
|
|
||||||
- [`memory_usage`](memory_usage.md) - the number of bytes held by the document
|
- [`memory_usage`](memory_usage.md) - the number of bytes held by the document
|
||||||
- [`shrink_to_fit`](shrink_to_fit.md) - release unused index capacity
|
- [`shrink_to_fit`](shrink_to_fit.md) - release unused index capacity
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#the-idea) - the flat node index and its memory
|
- [Zero-copy JSON views](../../features/json_view/index.md#the-idea) - the flat node index and its memory
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ document (`#!cpp owns_source() == false`) is only valid while the buffer it was
|
|||||||
- [`parse`](parse.md) - deserialize from a compatible input
|
- [`parse`](parse.md) - deserialize from a compatible input
|
||||||
- [`parse_copy`](parse_copy.md) - deserialize a copy of a compatible input, always owned
|
- [`parse_copy`](parse_copy.md) - deserialize a copy of a compatible input, always owned
|
||||||
- [`source`](source.md) - the parsed text
|
- [`source`](source.md) - the parsed text
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#ownership-and-lifetime) - who owns the source text and for how long
|
- [Zero-copy JSON views](../../features/json_view/lifetime.md) - who owns the source text and for how long
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ told apart from other class-type iterators, so their range is read into an owned
|
|||||||
range (e.g. of a `#!cpp std::list<char>`).
|
range (e.g. of a `#!cpp std::list<char>`).
|
||||||
|
|
||||||
See [`owns_source`](owns_source.md) to check which happened after a call, and the
|
See [`owns_source`](owns_source.md) to check which happened after a call, and the
|
||||||
[feature page](../../features/json_view.md) for the reasoning.
|
[feature page](../../features/json_view/index.md) for the reasoning.
|
||||||
|
|
||||||
**Numbers.** As for [`BasicJsonType::parse()`](../basic_json/parse.md), an integer literal too large for the 64-bit
|
**Numbers.** As for [`BasicJsonType::parse()`](../basic_json/parse.md), an integer literal too large for the 64-bit
|
||||||
integer type becomes a floating-point value.
|
integer type becomes a floating-point value.
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ compile; see [`parse`](parse.md#notes).
|
|||||||
|
|
||||||
- [`parse`](parse.md) - deserialize from a compatible input, borrowing it where possible
|
- [`parse`](parse.md) - deserialize from a compatible input, borrowing it where possible
|
||||||
- [`owns_source`](owns_source.md) - return whether the document holds its own copy of the text
|
- [`owns_source`](owns_source.md) - return whether the document holds its own copy of the text
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#ownership-and-lifetime) - borrowing or copying the source text
|
- [Zero-copy JSON views](../../features/json_view/lifetime.md) - borrowing or copying the source text
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ Like [`set`](set.md) on a member or an element, `push_back` never moves an exist
|
|||||||
- [`root`](root.md) - the view of the root value
|
- [`root`](root.md) - the view of the root value
|
||||||
- [`BasicJsonType::push_back`](../basic_json/push_back.md) - the corresponding function of `basic_json`
|
- [`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
|
- [Edits](index.md#edits) - what an edit guarantees, for every overload
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#editing-a-document) - what an edit guarantees and costs
|
- [Zero-copy JSON views](../../features/json_view/editing.md) - what an edit guarantees and costs
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ compile; see [`parse`](parse.md#notes).
|
|||||||
- [`parse`](parse.md) - deserialize from a compatible input
|
- [`parse`](parse.md) - deserialize from a compatible input
|
||||||
- [`root`](root.md) - the view of the root value
|
- [`root`](root.md) - the view of the root value
|
||||||
- [`load`](load.md) - read a document from an image instead of parsing JSON text
|
- [`load`](load.md) - read a document from an image instead of parsing JSON text
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#ownership-and-lifetime) - reusing a document and what it invalidates
|
- [Zero-copy JSON views](../../features/json_view/lifetime.md) - reusing a document and what it invalidates
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ auto v = doc.root();
|
|||||||
returned view is valid under the same conditions as any other view of the document — see
|
returned view is valid under the same conditions as any other view of the document — see
|
||||||
[Object inspection](../basic_json_view/index.md) — in particular, it is invalidated by the next
|
[Object inspection](../basic_json_view/index.md) — in particular, it is invalidated by the next
|
||||||
[`read()`](read.md) or [`shrink_to_fit()`](shrink_to_fit.md) on this document. A view can dangle, like a
|
[`read()`](read.md) or [`shrink_to_fit()`](shrink_to_fit.md) on this document. A view can dangle, like a
|
||||||
`#!cpp std::string_view`; see [Ownership and lifetime](../../features/json_view.md#ownership-and-lifetime).
|
`#!cpp std::string_view`; see [Ownership and lifetime](../../features/json_view/lifetime.md).
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ big-endian reader could not interpret correctly.
|
|||||||
- [`load`](load.md) - read an image written by `save()`
|
- [`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
|
- [`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
|
- [`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
|
- [Images](../../features/json_view/images.md) - why and when to use images
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ one-node scalar.
|
|||||||
- [`basic_json_view::dump`](../basic_json_view/dump.md) - serialize the document, keeping an untouched number's
|
- [`basic_json_view::dump`](../basic_json_view/dump.md) - serialize the document, keeping an untouched number's
|
||||||
spelling with `#!cpp number_format::source`
|
spelling with `#!cpp number_format::source`
|
||||||
- [Edits](index.md#edits) - what an edit guarantees, for every overload
|
- [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
|
- [Editing a document](../../features/json_view/editing.md) - why editable documents keep the source
|
||||||
text's order and number spelling
|
text's order and number spelling
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ possible would be an internal optimization only, not a guarantee to rely on.
|
|||||||
- [`node_count`](node_count.md) - the number of index entries
|
- [`node_count`](node_count.md) - the number of index entries
|
||||||
- [`memory_usage`](memory_usage.md) - the number of bytes held by the document
|
- [`memory_usage`](memory_usage.md) - the number of bytes held by the document
|
||||||
- [`root`](root.md) - the view of the root value
|
- [`root`](root.md) - the view of the root value
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#ownership-and-lifetime) - what releasing memory invalidates
|
- [Zero-copy JSON views](../../features/json_view/lifetime.md) - what releasing memory invalidates
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ is; see [`owns_source`](owns_source.md).
|
|||||||
|
|
||||||
- [`owns_source`](owns_source.md) - return whether the document holds its own copy of the text
|
- [`owns_source`](owns_source.md) - return whether the document holds its own copy of the text
|
||||||
- [`source_offset`](../basic_json_view/source_offset.md) - byte offset of a value in the source text
|
- [`source_offset`](../basic_json_view/source_offset.md) - byte offset of a value in the source text
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#ownership-and-lifetime) - who owns the source text and for how long
|
- [Zero-copy JSON views](../../features/json_view/lifetime.md) - who owns the source text and for how long
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -68,9 +68,10 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
- Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if `ptr` cannot be resolved
|
- Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if `ptr` cannot be resolved
|
||||||
because a reference token is used on a primitive value.
|
because a reference token is used on a primitive value.
|
||||||
|
|
||||||
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
||||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
Pointer path of the value within the document, and with
|
||||||
`JSON_DIAGNOSTICS` enabled.
|
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
|
||||||
|
[`materialize()`](materialize.md) sets them).
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -91,7 +92,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
|||||||
|
|
||||||
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
|
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
|
||||||
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
|
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
|
||||||
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
|
`operator[]` and [Duplicate keys](../../features/json_view/differences.md#duplicate-keys) for the reasons and for how to get
|
||||||
the last value.
|
the last value.
|
||||||
|
|
||||||
Unlike [`operator[]`](operator[].md), which returns a [discarded](is_discarded.md) view for a missing key or an
|
Unlike [`operator[]`](operator[].md), which returns a [discarded](is_discarded.md) view for a missing key or an
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is
|
Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is
|
||||||
[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object.
|
[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object.
|
||||||
|
|
||||||
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||||
`JSON_DIAGNOSTICS` enabled.
|
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ placeholder for "no value yet" and later be assigned a real view.
|
|||||||
- [`is_discarded`](is_discarded.md) - return whether the view is invalid
|
- [`is_discarded`](is_discarded.md) - return whether the view is invalid
|
||||||
- [`root`](../basic_json_document/root.md) - the view of a document's root value
|
- [`root`](../basic_json_document/root.md) - the view of a document's root value
|
||||||
- [`view_mode`](../view_mode.md) - whether a document and its views can be edited
|
- [`view_mode`](../view_mode.md) - whether a document and its views can be edited
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#ownership-and-lifetime) - how long a view stays valid
|
- [Zero-copy JSON views](../../features/json_view/lifetime.md) - how long a view stays valid
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ No-throw guarantee: this function never throws exceptions.
|
|||||||
|
|
||||||
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
|
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
|
||||||
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
|
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
|
||||||
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
|
`operator[]` and [Duplicate keys](../../features/json_view/differences.md#duplicate-keys) for the reasons and for how to get
|
||||||
the last value.
|
the last value.
|
||||||
|
|
||||||
Overload 1 always returns `#!cpp false` when the value is not an object — including a [discarded](is_discarded.md)
|
Overload 1 always returns `#!cpp false` when the value is not an object — including a [discarded](is_discarded.md)
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ except that `#!cpp -0` is written as `#!cpp 0`, the way [`BasicJsonType::parse()
|
|||||||
`#!cpp number_format::source` copies every number exactly as written in the source text instead, with no exception
|
`#!cpp number_format::source` copies every number exactly as written in the source text instead, with no exception
|
||||||
for `#!cpp -0` — `#!cpp 1.50`, `#!cpp 1E2`, `#!cpp -0.0`, `#!cpp -0`, or all digits of an integer literal with more
|
for `#!cpp -0` — `#!cpp 1.50`, `#!cpp 1E2`, `#!cpp -0.0`, `#!cpp -0`, or all digits of an integer literal with more
|
||||||
digits than any number type holds (such a literal is itself classified as a float, see
|
digits than any number type holds (such a literal is itself classified as a float, see
|
||||||
[What is different](../../features/json_view.md#what-is-different)) — something `BasicJsonType` cannot do, since
|
[What is different](../../features/json_view/differences.md)) — something `BasicJsonType` cannot do, since
|
||||||
parsing already reduces every number to its parsed value.
|
parsing already reduces every number to its parsed value.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
@@ -98,7 +98,7 @@ parsing already reduces every number to its parsed value.
|
|||||||
- [`operator<<`](operator_ltlt.md) - serialize this value to a stream
|
- [`operator<<`](operator_ltlt.md) - serialize this value to a stream
|
||||||
- [`materialize`](materialize.md) - build a `BasicJsonType` value, e.g. to use `BasicJsonType::dump`'s `error_handler`
|
- [`materialize`](materialize.md) - build a `BasicJsonType` value, e.g. to use `BasicJsonType::dump`'s `error_handler`
|
||||||
- [`BasicJsonType::dump`](../basic_json/dump.md) - the corresponding function of `basic_json`
|
- [`BasicJsonType::dump`](../basic_json/dump.md) - the corresponding function of `basic_json`
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#writing-a-view-back) - writing a view back without building a tree
|
- [Zero-copy JSON views](../../features/json_view/reading.md#writing-a-view-back) - writing a view back without building a tree
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ constant time on average.
|
|||||||
|
|
||||||
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
|
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
|
||||||
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
|
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
|
||||||
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
|
`operator[]` and [Duplicate keys](../../features/json_view/differences.md#duplicate-keys) for the reasons and for how to get
|
||||||
the last value.
|
the last value.
|
||||||
|
|
||||||
Unlike [`BasicJsonType::find`](../basic_json/find.md), which always returns `#!cpp end()` for a non-object type, this
|
Unlike [`BasicJsonType::find`](../basic_json/find.md), which always returns `#!cpp end()` for a non-object type, this
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is
|
Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is
|
||||||
[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object.
|
[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object.
|
||||||
|
|
||||||
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||||
`JSON_DIAGNOSTICS` enabled.
|
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ For the types below, the conversion works directly on the flat index — no `Bas
|
|||||||
|
|
||||||
- `#!cpp bool`
|
- `#!cpp bool`
|
||||||
- arithmetic types other than `#!cpp bool` (for a boolean, `#!cpp 0`/`#!cpp 1`, exactly as for
|
- arithmetic types other than `#!cpp bool` (for a boolean, `#!cpp 0`/`#!cpp 1`, exactly as for
|
||||||
[`BasicJsonType::get<T>()`](../basic_json/get.md))
|
[`BasicJsonType::get<T>()`](../basic_json/get.md#notes), except for `number_integer_t`, `number_unsigned_t` and
|
||||||
|
`number_float_t`, which throw `type_error.302` for a boolean, as `BasicJsonType::get<T>()` does)
|
||||||
- `#!cpp std::nullptr_t`
|
- `#!cpp std::nullptr_t`
|
||||||
- `#!cpp std::basic_string<char, Traits, Alloc>` (including `string_t`) — a copy of the string
|
- `#!cpp std::basic_string<char, Traits, Alloc>` (including `string_t`) — a copy of the string
|
||||||
- [`string_view_t`](index.md#member-types) — **no copy**: the returned view points into the document's
|
- [`string_view_t`](index.md#member-types) — **no copy**: the returned view points into the document's
|
||||||
@@ -38,14 +39,14 @@ as [`BasicJsonType::parse()`](../basic_json/parse.md) does), as well as the type
|
|||||||
accept views, e.g. with [`is_json_view`](../is_json_view.md). A `from_json()` that also accepts types that are no JSON
|
accept views, e.g. with [`is_json_view`](../is_json_view.md). A `from_json()` that also accepts types that are no JSON
|
||||||
values at all, such as the unconstrained `#!cpp template<typename BasicJsonType> void from_json(const BasicJsonType&,
|
values at all, such as the unconstrained `#!cpp template<typename BasicJsonType> void from_json(const BasicJsonType&,
|
||||||
T&)` of [arbitrary type conversions](../../features/arbitrary_types.md), is not called with a view: it may use
|
T&)` of [arbitrary type conversions](../../features/arbitrary_types.md), is not called with a view: it may use
|
||||||
`BasicJsonType` functions a view does not have. See [Writing generic code](../../features/json_view.md#writing-generic-code).
|
`BasicJsonType` functions a view does not have. See [Writing generic code](../../features/json_view/generic_code.md).
|
||||||
|
|
||||||
!!! warning "Duplicate keys"
|
!!! warning "Duplicate keys"
|
||||||
|
|
||||||
Such a `from_json()` looks members up in the view, where a repeated key finds the **first** member, while
|
Such a `from_json()` looks members up in the view, where a repeated key finds the **first** member, while
|
||||||
`#!cpp materialize().get<T>()` sees the **last** one, as [`BasicJsonType::parse()`](../basic_json/parse.md)
|
`#!cpp materialize().get<T>()` sees the **last** one, as [`BasicJsonType::parse()`](../basic_json/parse.md)
|
||||||
keeps it: for `#!cpp {"x":1,"x":2}`, `#!cpp get<point>()` reads `#!cpp x == 1`. See
|
keeps it: for `#!cpp {"x":1,"x":2}`, `#!cpp get<point>()` reads `#!cpp x == 1`. See
|
||||||
[Duplicate keys](../../features/json_view.md#duplicate-keys).
|
[Duplicate keys](../../features/json_view/differences.md#duplicate-keys).
|
||||||
|
|
||||||
Every other `T` — user types whose `from_json()` is written for `BasicJsonType` only, types without a default
|
Every other `T` — user types whose `from_json()` is written for `BasicJsonType` only, types without a default
|
||||||
constructor, `#!cpp std::tuple`s holding references, ... — is converted by `#!cpp materialize().get<T>()`: the subtree is built into a real `BasicJsonType`
|
constructor, `#!cpp std::tuple`s holding references, ... — is converted by `#!cpp materialize().get<T>()`: the subtree is built into a real `BasicJsonType`
|
||||||
@@ -80,14 +81,13 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
- For every other `T`: whatever [`materialize().get<T>()`](../basic_json/get.md) throws — typically `type_error.302`,
|
- For every other `T`: whatever [`materialize().get<T>()`](../basic_json/get.md) throws — typically `type_error.302`,
|
||||||
or whatever a user-provided `from_json()` throws.
|
or whatever a user-provided `from_json()` throws.
|
||||||
|
|
||||||
None of the exceptions thrown directly by this function or by a `from_json()` that reads the view (the first four
|
The exceptions thrown directly by this function or by a `from_json()` that reads the view (the first four bullets
|
||||||
bullets above) carry a
|
above) carry the context of those of `BasicJsonType`: with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the
|
||||||
[`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType` value to point at. An
|
JSON Pointer path of the value within the document, and with
|
||||||
exception thrown while converting through `materialize()` (the last bullet) is different: it is thrown by a real
|
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text. An exception
|
||||||
`BasicJsonType` value, so it **does** carry a `JSON_DIAGNOSTICS` path if `BasicJsonType` was built with it enabled.
|
thrown while converting through `materialize()` (the last bullet) is thrown by the materialized `BasicJsonType` value
|
||||||
That path is relative to the root of the view's subtree, because `materialize()` builds only the subtree (with parent
|
instead: its path is relative to the root of the view's subtree, because `materialize()` builds only the subtree
|
||||||
pointers): it is empty if the failing value is the subtree's root itself, and it never is the path within the whole
|
(with parent pointers), so it is empty if the failing value is the subtree's root itself.
|
||||||
document.
|
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ document.
|
|||||||
- [`number_token`](number_token.md) - a number's token text, without a copy
|
- [`number_token`](number_token.md) - a number's token text, without a copy
|
||||||
- [`materialize`](materialize.md) - build the `BasicJsonType` value of this subtree
|
- [`materialize`](materialize.md) - build the `BasicJsonType` value of this subtree
|
||||||
- [`BasicJsonType::get`](../basic_json/get.md) - the corresponding function of `basic_json`
|
- [`BasicJsonType::get`](../basic_json/get.md) - the corresponding function of `basic_json`
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#getting-values-out-without-copying) - which conversions avoid building a basic_json value
|
- [Zero-copy JSON views](../../features/json_view/reading.md#getting-values-out-without-copying) - which conversions avoid building a basic_json value
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a string; example:
|
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a string; example:
|
||||||
`"type must be string, but is array"`.
|
`"type must be string, but is array"`.
|
||||||
|
|
||||||
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||||
`JSON_DIAGNOSTICS` enabled.
|
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ since floating-point values and unescaped strings are read from the source lazil
|
|||||||
- [`get`](get.md) - convert the value to a given type (`#!cpp get<string_view_t>()` is equivalent to this function)
|
- [`get`](get.md) - convert the value to a given type (`#!cpp get<string_view_t>()` is equivalent to this function)
|
||||||
- [`number_token`](number_token.md) - a number's token text, without a copy
|
- [`number_token`](number_token.md) - a number's token text, without a copy
|
||||||
- [`BasicJsonType::get_ref`](../basic_json/get_ref.md) - the closest counterpart of `basic_json`
|
- [`BasicJsonType::get_ref`](../basic_json/get_ref.md) - the closest counterpart of `basic_json`
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#getting-values-out-without-copying) - borrowing strings instead of copying them
|
- [Zero-copy JSON views](../../features/json_view/reading.md#getting-values-out-without-copying) - borrowing strings instead of copying them
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ them with those of a default-constructed `T`.
|
|||||||
|
|
||||||
- [`get`](get.md) - convert the value to a given type
|
- [`get`](get.md) - convert the value to a given type
|
||||||
- [`BasicJsonType::get_to`](../basic_json/get_to.md) - the corresponding function of `basic_json`
|
- [`BasicJsonType::get_to`](../basic_json/get_to.md) - the corresponding function of `basic_json`
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#getting-values-out-without-copying) - which conversions avoid building a basic_json value
|
- [Zero-copy JSON views](../../features/json_view/reading.md#getting-values-out-without-copying) - which conversions avoid building a basic_json value
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ still refers to it — including ones taken before the change — reads the new
|
|||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
- [Zero-copy JSON views](../../features/json_view.md) - guide to documents and views
|
- [Zero-copy JSON views](../../features/json_view/index.md) - guide to documents and views
|
||||||
- [`basic_json_document`](../basic_json_document/index.md) - the document that owns the parsed data
|
- [`basic_json_document`](../basic_json_document/index.md) - the document that owns the parsed data
|
||||||
- [`json_view`](../json_view.md) - and [`json_editable_view`](../json_editable_view.md) - the default specializations
|
- [`json_view`](../json_view.md) - and [`json_editable_view`](../json_editable_view.md) - the default specializations
|
||||||
- [`materialize`](materialize.md) - build a `basic_json` value from a view
|
- [`materialize`](materialize.md) - build a `basic_json` value from a view
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ A `basic_json_view` is not convertible to `#!cpp bool`: such a conversion would
|
|||||||
- [`(constructor)`](basic_json_view.md) - the default constructor creates a discarded view
|
- [`(constructor)`](basic_json_view.md) - the default constructor creates a discarded view
|
||||||
- [`basic_json_document::is_discarded`](../basic_json_document/is_discarded.md) - return whether the last parse failed
|
- [`basic_json_document::is_discarded`](../basic_json_document/is_discarded.md) - return whether the last parse failed
|
||||||
- [`BasicJsonType::is_discarded`](../basic_json/is_discarded.md) - the corresponding function of `basic_json`
|
- [`BasicJsonType::is_discarded`](../basic_json/is_discarded.md) - the corresponding function of `basic_json`
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#what-is-different) - how views differ from basic_json
|
- [Zero-copy JSON views](../../features/json_view/differences.md) - how views differ from basic_json
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ never caches the result.
|
|||||||
- [`root`](../basic_json_document/root.md) - the view of a document's root value
|
- [`root`](../basic_json_document/root.md) - the view of a document's root value
|
||||||
- [`BasicJsonType::parse`](../basic_json/parse.md) - build a `BasicJsonType` value directly from a JSON text
|
- [`BasicJsonType::parse`](../basic_json/parse.md) - build a `BasicJsonType` value directly from a JSON text
|
||||||
- [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) - source positions on parsed values
|
- [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) - source positions on parsed values
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#getting-values-out-without-copying) - when to build a basic_json value from a view
|
- [Zero-copy JSON views](../../features/json_view/reading.md#getting-values-out-without-copying) - when to build a basic_json value from a view
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ source
|
|||||||
- [`number_token`](number_token.md) - get a single number's token text without dumping the whole value
|
- [`number_token`](number_token.md) - get a single number's token text without dumping the whole value
|
||||||
- [`BasicJsonType::error_handler_t`](../basic_json/error_handler_t.md) - the analogous enumeration for
|
- [`BasicJsonType::error_handler_t`](../basic_json/error_handler_t.md) - the analogous enumeration for
|
||||||
`BasicJsonType::dump`'s decoding-error behavior
|
`BasicJsonType::dump`'s decoding-error behavior
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#writing-a-view-back) - writing numbers as they appeared in the source
|
- [Zero-copy JSON views](../../features/json_view/reading.md#writing-a-view-back) - writing numbers as they appeared in the source
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a number; example:
|
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a number; example:
|
||||||
`"type must be number, but is string"`.
|
`"type must be number, but is string"`.
|
||||||
|
|
||||||
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||||
`JSON_DIAGNOSTICS` enabled.
|
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ buffer — and is valid exactly as long as that text is, see the [validity rules
|
|||||||
- [`BasicJsonType::number_integer_t`](../basic_json/number_integer_t.md),
|
- [`BasicJsonType::number_integer_t`](../basic_json/number_integer_t.md),
|
||||||
[`number_unsigned_t`](../basic_json/number_unsigned_t.md), [`number_float_t`](../basic_json/number_float_t.md) - the
|
[`number_unsigned_t`](../basic_json/number_unsigned_t.md), [`number_float_t`](../basic_json/number_float_t.md) - the
|
||||||
number types `#!cpp get<T>()` and `materialize()` convert into
|
number types `#!cpp get<T>()` and `materialize()` convert into
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#getting-values-out-without-copying) - reading a number exactly as written
|
- [Zero-copy JSON views](../../features/json_view/reading.md#getting-values-out-without-copying) - reading a number exactly as written
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -73,9 +73,10 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
- [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in `ptr` is not a
|
- [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in `ptr` is not a
|
||||||
number.
|
number.
|
||||||
|
|
||||||
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
||||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
Pointer path of the value within the document, and with
|
||||||
`JSON_DIAGNOSTICS` enabled.
|
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
|
||||||
|
[`materialize()`](materialize.md) sets them).
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -119,7 +120,7 @@ document.
|
|||||||
[`materialize()`](materialize.md) or iterate the members with [`items()`](items.md) and keep the last match.
|
[`materialize()`](materialize.md) or iterate the members with [`items()`](items.md) and keep the last match.
|
||||||
The key hash index of a larger object (128 members or more) leads to the first member of a key as well.
|
The key hash index of a larger object (128 members or more) leads to the first member of a key as well.
|
||||||
[`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including
|
[`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including
|
||||||
duplicates, in document order. See [Duplicate keys](../../features/json_view.md#duplicate-keys) and
|
duplicates, in document order. See [Duplicate keys](../../features/json_view/differences.md#duplicate-keys) and
|
||||||
[`size()`](size.md#notes).
|
[`size()`](size.md#notes).
|
||||||
|
|
||||||
!!! info "JSON pointer resolution"
|
!!! info "JSON pointer resolution"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ Neither overload builds a `BasicJsonType` value for a view to do the comparison
|
|||||||
compare by value across their types (`#!cpp 1 == 1.0`), and an object compares by its members, with duplicate keys
|
compare by value across their types (`#!cpp 1 == 1.0`), and an object compares by its members, with duplicate keys
|
||||||
resolved exactly as `parse()` resolves them — the last value, at the position of the first occurrence of the key.
|
resolved exactly as `parse()` resolves them — the last value, at the position of the first occurrence of the key.
|
||||||
This is not what [`operator[]`](operator[].md) and the other lookups return for a duplicate key: they find the *first*
|
This is not what [`operator[]`](operator[].md) and the other lookups return for a duplicate key: they find the *first*
|
||||||
member (see [Duplicate keys](../../features/json_view.md#duplicate-keys)).
|
member (see [Duplicate keys](../../features/json_view/differences.md#duplicate-keys)).
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ Linear, as [`dump`](dump.md#complexity).
|
|||||||
- [`dump`](dump.md) - serialize to a JSON-formatted string
|
- [`dump`](dump.md) - serialize to a JSON-formatted string
|
||||||
- [`operator<<(std::ostream&)`](../operator_ltlt.md) - the corresponding operator for `basic_json`
|
- [`operator<<(std::ostream&)`](../operator_ltlt.md) - the corresponding operator for `basic_json`
|
||||||
- [`JSON_NO_IO`](../macros/json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
- [`JSON_NO_IO`](../macros/json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#writing-a-view-back) - writing a view back without building a tree
|
- [Zero-copy JSON views](../../features/json_view/reading.md#writing-a-view-back) - writing a view back without building a tree
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ string, whose token length is not the same as its decoded [`size()`](size.md).
|
|||||||
|
|
||||||
- [`source`](../basic_json_document/source.md) - the parsed text
|
- [`source`](../basic_json_document/source.md) - the parsed text
|
||||||
- [`is_string`](is_string.md) - return whether the value is a string
|
- [`is_string`](is_string.md) - return whether the value is a string
|
||||||
- [Zero-copy JSON views](../../features/json_view.md#images) - images and the source text
|
- [Zero-copy JSON views](../../features/json_view/images.md) - images and the source text
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -61,9 +61,10 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
|||||||
`default_value` instead of throwing, exactly as [`BasicJsonType::value`](../basic_json/value.md) catches
|
`default_value` instead of throwing, exactly as [`BasicJsonType::value`](../basic_json/value.md) catches
|
||||||
`out_of_range` and returns `default_value`.
|
`out_of_range` and returns `default_value`.
|
||||||
|
|
||||||
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
||||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
Pointer path of the value within the document, and with
|
||||||
`JSON_DIAGNOSTICS` enabled.
|
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
|
||||||
|
[`materialize()`](materialize.md) sets them).
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
|||||||
|
|
||||||
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
|
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
|
||||||
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
|
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
|
||||||
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
|
`operator[]` and [Duplicate keys](../../features/json_view/differences.md#duplicate-keys) for the reasons and for how to get
|
||||||
the last value.
|
the last value.
|
||||||
|
|
||||||
!!! info "Differences to `at` and `operator[]`"
|
!!! info "Differences to `at` and `operator[]`"
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ view class.
|
|||||||
- [basic_json_view](basic_json_view/index.md) - a handle to one value of a document
|
- [basic_json_view](basic_json_view/index.md) - a handle to one value of a document
|
||||||
- [get](basic_json_view/get.md) - which conversions read the view directly
|
- [get](basic_json_view/get.md) - which conversions read the view directly
|
||||||
- [view_mode](view_mode.md) - read-only or editable
|
- [view_mode](view_mode.md) - read-only or editable
|
||||||
- [Writing generic code](../features/json_view.md#writing-generic-code) - functions and `from_json()` for views
|
- [Writing generic code](../features/json_view/generic_code.md) - functions and `from_json()` for views
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ specialization.
|
|||||||
|
|
||||||
- [`json_view`](json_view.md) - a view of a value of a `json_document`
|
- [`json_view`](json_view.md) - a view of a value of a `json_document`
|
||||||
- [`ordered_json_document`](ordered_json_document.md) - the corresponding document for `ordered_json`
|
- [`ordered_json_document`](ordered_json_document.md) - the corresponding document for `ordered_json`
|
||||||
- [Zero-copy JSON views](../features/json_view.md) - how to parse, inspect and edit a document without building a tree
|
- [Zero-copy JSON views](../features/json_view/index.md) - how to parse, inspect and edit a document without building a tree
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ This type is an **editable** [`basic_json_document`](basic_json_document/index.m
|
|||||||
[`json`](json.md) specialization: in addition to everything [`json_document`](json_document.md) offers,
|
[`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
|
[`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
|
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).
|
[Editing a document](../features/json_view/editing.md).
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ still referring to it seeing the change, see [Edits](basic_json_document/index.m
|
|||||||
- [`json_view`](json_view.md) - the corresponding read-only view
|
- [`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_view`](ordered_json_editable_view.md) - the corresponding view for
|
||||||
`ordered_json_editable_document`
|
`ordered_json_editable_document`
|
||||||
- [Zero-copy JSON views](../features/json_view.md#editing-a-document) - editing a document in place and writing it back
|
- [Zero-copy JSON views](../features/json_view/editing.md) - editing a document in place and writing it back
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ This type is a [`basic_json_view`](basic_json_view/index.md) of a value of a [`j
|
|||||||
|
|
||||||
- [`json_document`](json_document.md) - the document type this view refers into
|
- [`json_document`](json_document.md) - the document type this view refers into
|
||||||
- [`ordered_json_view`](ordered_json_view.md) - the corresponding view for `ordered_json_document`
|
- [`ordered_json_view`](ordered_json_view.md) - the corresponding view for `ordered_json_document`
|
||||||
- [Zero-copy JSON views](../features/json_view.md) - how to parse, inspect and edit a document without building a tree
|
- [Zero-copy JSON views](../features/json_view/index.md) - how to parse, inspect and edit a document without building a tree
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ Note that enabling this macro increases the size of every JSON value by two `std
|
|||||||
overhead to parsing, copying JSON value objects, and the generation of error messages for exceptions. It also causes
|
overhead to parsing, copying JSON value objects, and the generation of error messages for exceptions. It also causes
|
||||||
these values to be reported in those error messages.
|
these values to be reported in those error messages.
|
||||||
|
|
||||||
|
If [`JSON_DIAGNOSTICS`](json_diagnostics.md) is also set, the message contains the path and the byte range, such as
|
||||||
|
`(/1) (bytes 4-8)`. An exception about the root value has no path and only the range, such as `(bytes 0-4)`.
|
||||||
|
|
||||||
## Default definition
|
## Default definition
|
||||||
|
|
||||||
The default value is `0` (position diagnostics are switched off).
|
The default value is `0` (position diagnostics are switched off).
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ By default, `#!cpp JSON_NO_SIMD` is not defined, and vector instructions are use
|
|||||||
- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance) - how input
|
- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance) - how input
|
||||||
sources affect parsing performance
|
sources affect parsing performance
|
||||||
- [Serialization](../../features/serialization.md) - pretty-printing, UTF-8 handling, and number output
|
- [Serialization](../../features/serialization.md) - pretty-printing, UTF-8 handling, and number output
|
||||||
- [Zero-copy JSON views](../../features/json_view.md) - the zero-copy view, whose parser the macro also affects
|
- [Zero-copy JSON views](../../features/json_view/index.md) - the zero-copy view, whose parser the macro also affects
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ of object keys, instead of sorting them like [`json_document`](json_document.md)
|
|||||||
- [`ordered_json_view`](ordered_json_view.md) - a view of a value of an `ordered_json_document`
|
- [`ordered_json_view`](ordered_json_view.md) - a view of a value of an `ordered_json_document`
|
||||||
- [`json_document`](json_document.md) - the corresponding document for the default `json` specialization
|
- [`json_document`](json_document.md) - the corresponding document for the default `json` specialization
|
||||||
- [Object Order](../features/object_order.md) - preserving the insertion order of object keys
|
- [Object Order](../features/object_order.md) - preserving the insertion order of object keys
|
||||||
- [Zero-copy JSON views](../features/json_view.md) - how to parse, inspect and edit a document without building a tree
|
- [Zero-copy JSON views](../features/json_view/index.md) - how to parse, inspect and edit a document without building a tree
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ members [`set`](basic_json_document/set.md) added — instead of sorting them li
|
|||||||
specialization
|
specialization
|
||||||
- [Object Order](../features/object_order.md) - preserving the insertion order of object keys
|
- [Object Order](../features/object_order.md) - preserving the insertion order of object keys
|
||||||
- [Edits](basic_json_document/index.md#edits) - what an edit guarantees
|
- [Edits](basic_json_document/index.md#edits) - what an edit guarantees
|
||||||
- [Zero-copy JSON views](../features/json_view.md#editing-a-document) - editing a document in place and writing it back
|
- [Zero-copy JSON views](../features/json_view/editing.md) - editing a document in place and writing it back
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ 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 document type this view refers into
|
- [`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
|
- [`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`
|
- [`json_editable_view`](json_editable_view.md) - the corresponding view for `json_editable_document`
|
||||||
- [Zero-copy JSON views](../features/json_view.md#editing-a-document) - editing a document in place and writing it back
|
- [Zero-copy JSON views](../features/json_view/editing.md) - editing a document in place and writing it back
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ This type is a [`basic_json_view`](basic_json_view/index.md) of a value of an
|
|||||||
|
|
||||||
- [`ordered_json_document`](ordered_json_document.md) - the document type this view refers into
|
- [`ordered_json_document`](ordered_json_document.md) - the document type this view refers into
|
||||||
- [`json_view`](json_view.md) - the corresponding view for `json_document`
|
- [`json_view`](json_view.md) - the corresponding view for `json_document`
|
||||||
- [Zero-copy JSON views](../features/json_view.md) - how to parse, inspect and edit a document without building a tree
|
- [Zero-copy JSON views](../features/json_view/index.md) - how to parse, inspect and edit a document without building a tree
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ nor a [`json_editable_view`](json_editable_view.md). Views of the two modes comp
|
|||||||
- [basic_json_view](basic_json_view/index.md) - a handle to one value of a document
|
- [basic_json_view](basic_json_view/index.md) - a handle to one value of a document
|
||||||
- [json_editable_document](json_editable_document.md) - an editable document of `json`
|
- [json_editable_document](json_editable_document.md) - an editable document of `json`
|
||||||
- [(constructor)](basic_json_view/basic_json_view.md) - the conversion from a read-only to an editable view
|
- [(constructor)](basic_json_view/basic_json_view.md) - the conversion from a read-only to an editable view
|
||||||
- [Writing generic code](../features/json_view.md#writing-generic-code) - functions that accept views
|
- [Writing generic code](../features/json_view/generic_code.md) - functions that accept views
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* figures: jsonview (features/json_view.md; SVGs from scripts/figures/json_view_layout.py, stepper in js/fig-jsonview.js) */
|
/* figures: jsonview (features/json_view/index.md; SVGs from scripts/figures/json_view_layout.py, stepper in js/fig-jsonview.js) */
|
||||||
|
|
||||||
/* one colour per concept, the same in every figure: structure (object/array, views) blue, keys navy, strings green,
|
/* one colour per concept, the same in every figure: structure (object/array, views) blue, keys navy, strings green,
|
||||||
numbers orange, decoded strings purple, owned edit storage teal, dangling/invalid red */
|
numbers orange, decoded strings purple, owned edit storage teal, dangling/invalid red */
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ which avoids repeating it:
|
|||||||
[`number_token()`](../api/basic_json_view/number_token.md) returns a number exactly as it was written. Every other
|
[`number_token()`](../api/basic_json_view/number_token.md) returns a number exactly as it was written. Every other
|
||||||
type is obtained from a subtree that is built on demand with
|
type is obtained from a subtree that is built on demand with
|
||||||
[`materialize()`](../api/basic_json_view/materialize.md). See
|
[`materialize()`](../api/basic_json_view/materialize.md). See
|
||||||
[Zero-copy JSON views](json_view.md#getting-values-out-without-copying).
|
[Zero-copy JSON views](json_view/reading.md#getting-values-out-without-copying).
|
||||||
|
|
||||||
The library already knows how to convert to and from the scalar types and the STL containers (such as
|
The library already knows how to convert to and from the scalar types and the STL containers (such as
|
||||||
`#!cpp std::vector`, `#!cpp std::map`, `#!cpp std::array`, `#!cpp std::optional`, and many more). Converting a JSON
|
`#!cpp std::vector`, `#!cpp std::map`, `#!cpp std::array`, `#!cpp std::optional`, and many more). Converting a JSON
|
||||||
@@ -269,7 +269,7 @@ The conversions above are built in for standard types. To make the same syntax w
|
|||||||
- [`get_ref`](../api/basic_json/get_ref.md) - / [`get_ptr`](../api/basic_json/get_ptr.md) - access the stored value without copying
|
- [`get_ref`](../api/basic_json/get_ref.md) - / [`get_ptr`](../api/basic_json/get_ptr.md) - access the stored value without copying
|
||||||
- [Arbitrary types conversions](arbitrary_types.md) - support your own types
|
- [Arbitrary types conversions](arbitrary_types.md) - support your own types
|
||||||
- [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) - toggle implicit conversions
|
- [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) - toggle implicit conversions
|
||||||
- [Zero-copy JSON views](json_view.md) - get values out of a document or a view
|
- [Zero-copy JSON views](json_view/index.md) - get values out of a document or a view
|
||||||
- [`adl_serializer`](../api/adl_serializer/index.md) - the default serializer behind the conversions
|
- [`adl_serializer`](../api/adl_serializer/index.md) - the default serializer behind the conversions
|
||||||
- [`type`](../api/basic_json/type.md) - query the type of a value
|
- [`type`](../api/basic_json/type.md) - query the type of a value
|
||||||
- [`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md) - toggle conversion to tuples of references
|
- [`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md) - toggle conversion to tuples of references
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
There are several ways to create a JSON value in memory. This page gives an overview; to read a value from JSON text
|
There are several ways to create a JSON value in memory. This page gives an overview; to read a value from JSON text
|
||||||
instead, see [parsing](parsing/index.md). A JSON text can also be parsed into a
|
instead, see [parsing](parsing/index.md). A JSON text can also be parsed into a
|
||||||
[document](../api/basic_json_document/index.md) and read through a [view](../api/basic_json_view/index.md) instead of
|
[document](../api/basic_json_document/index.md) and read through a [view](../api/basic_json_view/index.md) instead of
|
||||||
into a value; see [Zero-copy JSON views](json_view.md).
|
into a value; see [Zero-copy JSON views](json_view/index.md).
|
||||||
|
|
||||||
## From C++ values
|
## From C++ values
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ string `#!json "42"`.
|
|||||||
- [`operator""_json`](../api/operator_literal_json.md) - the `_json` literal
|
- [`operator""_json`](../api/operator_literal_json.md) - the `_json` literal
|
||||||
- [Converting values](conversions.md) - which C++ types can be used
|
- [Converting values](conversions.md) - which C++ types can be used
|
||||||
- [Parsing](parsing/index.md) - create a value from JSON text
|
- [Parsing](parsing/index.md) - create a value from JSON text
|
||||||
- [Zero-copy JSON views](json_view.md) - create a document and a view from JSON text instead of a value
|
- [Zero-copy JSON views](json_view/index.md) - create a document and a view from JSON text instead of a value
|
||||||
- [`basic_json`](../api/basic_json/index.md) - the class template and its member functions
|
- [`basic_json`](../api/basic_json/index.md) - the class template and its member functions
|
||||||
- [Types](types/index.md) - the JSON value types
|
- [Types](types/index.md) - the JSON value types
|
||||||
- [`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md) - change how a single braced value is interpreted
|
- [`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md) - change how a single braced value is interpreted
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ C++ types, and finally serialize it again.
|
|||||||
[SAX interface](parsing/sax_interface.md), [error handling](parsing/parse_exceptions.md),
|
[SAX interface](parsing/sax_interface.md), [error handling](parsing/parse_exceptions.md),
|
||||||
[large and partial input](parsing/large_and_partial_input.md), and
|
[large and partial input](parsing/large_and_partial_input.md), and
|
||||||
[parsing untrusted input](parsing/untrusted_input.md).
|
[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;
|
- [Zero-copy JSON views](json_view/index.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.
|
strings and numbers stay in the input and are only decoded when needed.
|
||||||
- [Editable documents](json_view.md#editing-a-document) — modify a parsed document without building a `json` tree.
|
- [Editable documents](json_view/editing.md) — modify a parsed document without building a `json` tree.
|
||||||
- [Images](json_view.md#images) — save a parsed document and load it again without parsing it.
|
- [Images](json_view/images.md) — save a parsed document and load it again without parsing it.
|
||||||
- [Comments](comments.md) and [trailing commas](trailing_commas.md) — opt-in relaxations of the JSON grammar.
|
- [Comments](comments.md) and [trailing commas](trailing_commas.md) — opt-in relaxations of the JSON grammar.
|
||||||
|
|
||||||
## Accessing and modifying values
|
## Accessing and modifying values
|
||||||
|
|||||||
@@ -1,557 +0,0 @@
|
|||||||
# Zero-copy JSON views
|
|
||||||
|
|
||||||
`#!cpp <nlohmann/json_view.hpp>` adds a read-only, non-owning way to look at a parsed JSON text, as an alternative to
|
|
||||||
building a [`basic_json`](../api/basic_json/index.md) tree with [`parse()`](../api/basic_json/parse.md).
|
|
||||||
|
|
||||||
## The problem
|
|
||||||
|
|
||||||
[`basic_json::parse()`](../api/basic_json/parse.md) builds a tree of `basic_json` values: one allocation for every
|
|
||||||
array and object, and every string copied into its own `std::string`. That is the right trade-off when the program
|
|
||||||
goes on to read and write the value freely, but it does more work than necessary when only a small part of a large
|
|
||||||
JSON text is actually needed, or when the same text is parsed over and over (many small messages, for instance) and
|
|
||||||
most of the resulting tree is thrown away almost immediately.
|
|
||||||
|
|
||||||
## The idea
|
|
||||||
|
|
||||||
[`basic_json_document::parse()`](../api/basic_json_document/parse.md) parses the same JSON grammar, with the same
|
|
||||||
options, but instead of a tree it builds a flat index of the values it found: one
|
|
||||||
[16-byte entry](../home/architecture.md#node-index-of-json-views) per value (and one per object key), in document
|
|
||||||
order. Integers are converted while parsing and stored in their entry. Floating-point numbers and strings are
|
|
||||||
referenced in place in the source text: a float is converted when it is read, and only strings that contain escape
|
|
||||||
sequences are decoded, into one shared buffer owned by the document.
|
|
||||||
|
|
||||||
[`basic_json_view`](../api/basic_json_view/index.md) is a small, trivially copyable handle (two pointers) into that
|
|
||||||
index. It gives you the read-only, type-inspection part of the `basic_json` interface —
|
|
||||||
[`type()`](../api/basic_json_view/type.md) and the `is_*()` predicates,
|
|
||||||
[`size()`](../api/basic_json_view/size.md)/[`empty()`](../api/basic_json_view/empty.md) — as well as element access
|
|
||||||
([`operator[]`](../api/basic_json_view/operator%5B%5D.md), [`at`](../api/basic_json_view/at.md),
|
|
||||||
[`front`](../api/basic_json_view/front.md)/[`back`](../api/basic_json_view/back.md)), lookup
|
|
||||||
([`find`](../api/basic_json_view/find.md), [`contains`](../api/basic_json_view/contains.md),
|
|
||||||
[`count`](../api/basic_json_view/count.md)), and iteration
|
|
||||||
([`begin`](../api/basic_json_view/begin.md)/[`end`](../api/basic_json_view/end.md),
|
|
||||||
[`items`](../api/basic_json_view/items.md)) — without ever allocating a `basic_json` value. When you do need an
|
|
||||||
actual `basic_json` value for a subtree, [`materialize()`](../api/basic_json_view/materialize.md) builds exactly the
|
|
||||||
one [`parse()`](../api/basic_json/parse.md) would have produced for it.
|
|
||||||
|
|
||||||
The same small text, parsed both ways:
|
|
||||||
|
|
||||||
<div class="nj-fig-wrap" markdown>
|
|
||||||
--8<-- "images/json_view_layout.svg"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="nj-caption" markdown>Top: the tree of separate heap allocations that `basic_json::parse()` builds. Bottom: `json_document::parse()` keeps the source text and builds one flat index of 16-byte nodes that point back into it, plus a buffer of decoded strings.</p>
|
|
||||||
|
|
||||||
## How to use it
|
|
||||||
|
|
||||||
Include `<nlohmann/json_view.hpp>` in addition to (or instead of) `<nlohmann/json.hpp>`. Parse into a
|
|
||||||
[`json_document`](../api/json_document.md), inspect its [`root()`](../api/basic_json_document/root.md), and
|
|
||||||
`materialize()` when you need a real value. `materialize()` works on any view, not just the root, so you can turn just
|
|
||||||
the subtree you need into a value:
|
|
||||||
|
|
||||||
??? example "Example: parse a document, inspect its root, and materialize it"
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
--8<-- "examples/json_document.cpp"
|
|
||||||
```
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```json
|
|
||||||
--8<-- "examples/json_document.output"
|
|
||||||
```
|
|
||||||
|
|
||||||
[`ordered_json_document`](../api/ordered_json_document.md) is the equivalent for
|
|
||||||
[`ordered_json`](../api/ordered_json.md), just as [`ordered_json`](../api/ordered_json.md) is to
|
|
||||||
[`json`](../api/json.md).
|
|
||||||
|
|
||||||
## Ownership and lifetime
|
|
||||||
|
|
||||||
A document either **borrows** the text it was parsed from, or **owns** its own copy of it; call
|
|
||||||
[`owns_source()`](../api/basic_json_document/owns_source.md) to find out which happened.
|
|
||||||
[`parse()`](../api/basic_json_document/parse.md) decides this from the value category and type of its argument (an
|
|
||||||
lvalue `#!cpp std::string` is borrowed; an rvalue `#!cpp std::string` is moved in, owned without a copy; a stream is
|
|
||||||
read into an owned buffer; and so on — see [`parse`'s Notes](../api/basic_json_document/parse.md#notes) for the full
|
|
||||||
table). [`parse_copy()`](../api/basic_json_document/parse_copy.md) always owns a copy, regardless of the input.
|
|
||||||
|
|
||||||
!!! warning "A borrowed document depends on your buffer"
|
|
||||||
|
|
||||||
If a document borrows its text, that text **must outlive the document** (and every view taken from it) and must
|
|
||||||
stay **unmodified** for as long as the document exists, since floating-point values and unescaped strings are
|
|
||||||
read from it lazily. Reading or writing through a view after the underlying buffer is gone is undefined behavior,
|
|
||||||
exactly as it would be for a dangling `#!cpp std::string_view`.
|
|
||||||
|
|
||||||
A view is valid only while all of the following hold:
|
|
||||||
|
|
||||||
- the document is alive,
|
|
||||||
- the document has not been re-parsed since the view was taken (with [`read()`](../api/basic_json_document/read.md)
|
|
||||||
or [`parse()`](../api/basic_json_document/parse.md) into it),
|
|
||||||
- the document has not had [`shrink_to_fit()`](../api/basic_json_document/shrink_to_fit.md) called on it since, and
|
|
||||||
- if the document borrows its source text, that text is still alive and unmodified.
|
|
||||||
|
|
||||||
Moving the `basic_json_document<>` itself is fine and does **not** invalidate its views: the index is a separate heap
|
|
||||||
allocation that keeps its address across the move. Take a fresh view from [`root()`](../api/basic_json_document/root.md)
|
|
||||||
whenever any of the other conditions above was not met.
|
|
||||||
|
|
||||||
<div class="nj-fig-wrap" markdown>
|
|
||||||
--8<-- "images/json_view_lifetime.svg"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="nj-caption" markdown>Four timelines of a document and its views, each bar showing when an object is alive or valid and when it dangles. Destroying the borrowed text, re-parsing, or shrinking the document invalidates the views taken before; moving the document does not.</p>
|
|
||||||
|
|
||||||
A view can dangle, exactly like a `#!cpp std::string_view`: nothing stops you from using it after its document is
|
|
||||||
gone. This is deliberate. A view is two pointers and trivially copyable; making it safe (a `#!cpp std::shared_ptr` in
|
|
||||||
the document and a `#!cpp std::weak_ptr` in each view) would make it larger, add reference counting to every copy,
|
|
||||||
and a `lock()` to every access — exactly the costs the view exists to avoid. What is caught is the obvious case:
|
|
||||||
because a view dies with its document, [`root()`](../api/basic_json_document/root.md) is not callable on a temporary
|
|
||||||
document: `#!cpp auto v = json_document::parse(text).root();` does not compile. Give the document a name first.
|
|
||||||
|
|
||||||
??? example "Example: borrowed and owned documents, and when views become invalid"
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
--8<-- "examples/json_view_ownership.cpp"
|
|
||||||
```
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
```json
|
|
||||||
--8<-- "examples/json_view_ownership.output"
|
|
||||||
```
|
|
||||||
|
|
||||||
## What is the same as `parse()`
|
|
||||||
|
|
||||||
- **Accept/reject.** [`accept()`](../api/basic_json_document/accept.md) and
|
|
||||||
[`parse()`](../api/basic_json_document/parse.md) accept and reject exactly the same inputs as
|
|
||||||
[`basic_json::accept()`](../api/basic_json/accept.md)/[`basic_json::parse()`](../api/basic_json/parse.md), with the
|
|
||||||
same `ignore_comments` and `ignore_trailing_commas` options.
|
|
||||||
- **Errors.** A failing parse throws the same exception — the same id, message, and position — because on failure
|
|
||||||
the library's own parser is run on the same bytes to produce the diagnostic. Valid input never pays for this, and
|
|
||||||
the replay builds no values: it costs about a lexing pass up to the error.
|
|
||||||
`#!cpp allow_exceptions == false` skips the replay and gives a
|
|
||||||
[discarded](../api/basic_json_document/is_discarded.md) document instead of throwing, just as it gives a discarded
|
|
||||||
value for `#!cpp basic_json::parse()`.
|
|
||||||
- **Number classification.** An integer literal that does not fit into the 64-bit integer type becomes a
|
|
||||||
floating-point value, exactly as it does for `#!cpp basic_json::parse()`.
|
|
||||||
- **Macros.** [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) and
|
|
||||||
[`JSON_NOEXCEPTION`](../api/macros/json_noexception.md)/[`JSON_THROW_USER`](../api/macros/json_throw_user.md)
|
|
||||||
behave the same way they do for `<nlohmann/json.hpp>`. These are the macros that change what the parser accepts or
|
|
||||||
how it reports errors. The others either do not apply ([`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md)
|
|
||||||
needs parent pointers in `basic_json` values) or only affect the values `materialize()` produces, through
|
|
||||||
`BasicJsonType`.
|
|
||||||
|
|
||||||
## What is different
|
|
||||||
|
|
||||||
- **Only 64-bit integers.** `basic_json_document<BasicJsonType>` requires `BasicJsonType::number_integer_t` and
|
|
||||||
`number_unsigned_t` to both be 64 bits wide; this is a compile-time `#!cpp static_assert`.
|
|
||||||
- **A 4 GiB input limit.** An input of 4294967280 bytes (4 GiB minus 16 bytes) or more throws
|
|
||||||
[`out_of_range.416`](../home/exceptions.md#jsonexceptionout_of_range416), a limit
|
|
||||||
`#!cpp basic_json::parse()` does not have. It keeps an index entry at 16 bytes: offsets and counts are 32 bits
|
|
||||||
wide. The limit is 16 bytes below 2^32, because the parser reads in 16-byte steps, and a position one step past the
|
|
||||||
end must still fit.
|
|
||||||
- **A stream is always read to its end.** There is no partial/streaming read of an `#!cpp std::istream`: the stream
|
|
||||||
is read to its end into a buffer the document owns, and parsing starts after that.
|
|
||||||
- **Fewer source positions after edits.** With
|
|
||||||
[`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) enabled,
|
|
||||||
[`materialize()`](../api/basic_json_view/materialize.md) sets the same positions as `#!cpp basic_json::parse()`.
|
|
||||||
In an [editable document](../api/json_editable_document.md), a value written by an edit has none, and neither has
|
|
||||||
an array or object whose elements changed or whose last element has none; a `basic_json` value keeps its positions
|
|
||||||
when it is changed. See [`materialize()`](../api/basic_json_view/materialize.md#notes).
|
|
||||||
- **Objects iterate in document order.** [`begin()`](../api/basic_json_view/begin.md)/
|
|
||||||
[`end()`](../api/basic_json_view/end.md) and [`items()`](../api/basic_json_view/items.md) visit an object's
|
|
||||||
members in the order they appear in the source text. `basic_json`'s default `object_t` is a `std::map`, which
|
|
||||||
sorts by key, so iterating a [`materialize()`](../api/basic_json_view/materialize.md)d value can print members in
|
|
||||||
a different order than iterating the view they came from.
|
|
||||||
- **Chained access is safe.** [`operator[]`](../api/basic_json_view/operator%5B%5D.md) with a missing key, an index
|
|
||||||
out of range, or an unresolvable JSON pointer returns a [discarded](../api/basic_json_view/is_discarded.md) view, and
|
|
||||||
`operator[]` on a discarded view returns a discarded view without throwing: `#!cpp v["a"]["b"][0]` can be tested
|
|
||||||
once at the end. Type errors on values that exist (a key on an array, an index on an object) still throw, and
|
|
||||||
[`at`](../api/basic_json_view/at.md) throws for every missing value.
|
|
||||||
- **Duplicate keys: lookups find the first member, `parse()` keeps the last.** See [Duplicate keys](#duplicate-keys)
|
|
||||||
below.
|
|
||||||
- **No [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) path.** Exceptions thrown by `basic_json_view`'s own
|
|
||||||
element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the
|
|
||||||
view has no `basic_json` value to point at, so the exception is created without one, regardless of how
|
|
||||||
`BasicJsonType` was built.
|
|
||||||
- **Ordering comparisons are not provided** by `basic_json_view` — there is no `#!cpp operator<`.
|
|
||||||
[`operator==`](../api/basic_json_view/operator_eq.md) and [`operator!=`](../api/basic_json_view/operator_ne.md) are
|
|
||||||
provided, though: two views, or a view and a `BasicJsonType` value, compare equal exactly when
|
|
||||||
[`materialize()`](../api/basic_json_view/materialize.md) or [`parse()`](../api/basic_json/parse.md) would produce
|
|
||||||
equal values for them, without ever building a tree to do it. For ordering, too,
|
|
||||||
[`materialize()`](../api/basic_json_view/materialize.md) is the way to get a value you can compare.
|
|
||||||
|
|
||||||
## Duplicate keys
|
|
||||||
|
|
||||||
!!! warning "A lookup in a view and in the parsed value can give different answers"
|
|
||||||
|
|
||||||
If an object in the source text repeats a key, [`operator[]`](../api/basic_json_view/operator%5B%5D.md),
|
|
||||||
[`at`](../api/basic_json_view/at.md), [`find`](../api/basic_json_view/find.md),
|
|
||||||
[`value`](../api/basic_json_view/value.md), [`contains`](../api/basic_json_view/contains.md), and JSON pointer
|
|
||||||
resolution all return the **first** member with that key. `basic_json::parse()` instead keeps the **last** value
|
|
||||||
of a repeated key, so for `#!cpp {"a":1,"a":2}`, `#!cpp view["a"]` is `#!cpp 1` while `#!cpp parse(text)["a"]` is
|
|
||||||
`#!cpp 2`. [`count`](../api/basic_json_view/count.md) counts every member with the key, so it is `#!cpp 2` here.
|
|
||||||
|
|
||||||
[`begin()`](../api/basic_json_view/begin.md)/[`end()`](../api/basic_json_view/end.md) and
|
|
||||||
[`items()`](../api/basic_json_view/items.md) visit *every* occurrence, in document order, and
|
|
||||||
[`size()`](../api/basic_json_view/size.md) counts all of them, so `#!cpp view.size()` can be larger than
|
|
||||||
`#!cpp view.materialize().size()`.
|
|
||||||
|
|
||||||
**Why the first?** A lookup can stop as soon as it finds a match. Returning the last member would force every lookup
|
|
||||||
to scan all members of the object, even when the key is found at the very first one: this made lookups in small
|
|
||||||
objects 1.6 to 3.4 times slower. The hash index of the keys of objects with 128 members or more leads to the first member of a
|
|
||||||
key as well. Other zero-copy parsers that index the source text, such as yyjson and simdjson, also return the first
|
|
||||||
member. RFC 8259 only says that names within an object SHOULD be unique and that the behavior of a receiver that sees
|
|
||||||
duplicates is unpredictable, so neither choice is wrong.
|
|
||||||
|
|
||||||
The same holds for [`get<T>()`](../api/basic_json_view/get.md) of a type whose `from_json()` reads the view, such as
|
|
||||||
the types of the `NLOHMANN_DEFINE_TYPE_*` macros: their members are looked up, so `#!cpp get<point>()` of
|
|
||||||
`#!cpp {"x":1,"x":2}` reads `#!cpp x == 1`, while `#!cpp materialize().get<point>()` reads `#!cpp 2`.
|
|
||||||
|
|
||||||
**What stays the same as `parse()`?** [`materialize()`](../api/basic_json_view/materialize.md) and
|
|
||||||
[`get<std::map<...>>()`](../api/basic_json_view/get.md) replay every member in order, so they keep the *last* value
|
|
||||||
exactly like `#!cpp basic_json::parse()` (at the position of the first occurrence of the key, for an
|
|
||||||
[`ordered_json`](../api/ordered_json.md)).
|
|
||||||
|
|
||||||
**How do I get the value `parse()` would give?** Either call `#!cpp view.materialize()` and look the key up in the
|
|
||||||
result, or iterate the members and keep the last match:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// the last member with the key "a", as parse() would keep it
|
|
||||||
json_view last;
|
|
||||||
for (const auto item : view.items())
|
|
||||||
{
|
|
||||||
if (item.key() == "a")
|
|
||||||
{
|
|
||||||
last = item.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you cannot trust the source text to have unique keys, check [`size()`](../api/basic_json_view/size.md) against the
|
|
||||||
number of distinct keys, or reject duplicates when iterating.
|
|
||||||
|
|
||||||
## Getting values out without copying
|
|
||||||
|
|
||||||
[`get<T>()`](../api/basic_json_view/get.md) converts most `T` directly from the flat index, without ever building a
|
|
||||||
`basic_json` value for the conversion: `#!cpp bool`, arithmetic types, `#!cpp std::nullptr_t`,
|
|
||||||
`#!cpp std::string`/other `#!cpp std::basic_string`s (copied once), `basic_json_view` itself, the standard containers
|
|
||||||
and other types the library converts (`#!cpp std::vector`, `#!cpp std::map`, `#!cpp std::list`, `#!cpp std::array`,
|
|
||||||
`#!cpp std::pair`, `#!cpp std::tuple`, `#!cpp std::optional`, ...), and the types of the `NLOHMANN_DEFINE_TYPE_*` and
|
|
||||||
`NLOHMANN_JSON_SERIALIZE_ENUM` macros. Types whose `from_json()` is written for `basic_json` only go through
|
|
||||||
[`materialize()`](../api/basic_json_view/materialize.md)`.get<T>()` instead: the subtree is built into a real
|
|
||||||
`basic_json` value first, exactly as [`parse()`](../api/basic_json/parse.md) would, and converted from there. See
|
|
||||||
[Writing generic code](#writing-generic-code) for how to write a `from_json()` that reads views.
|
|
||||||
|
|
||||||
Two conversions never copy at all:
|
|
||||||
|
|
||||||
- [`get_string()`](../api/basic_json_view/get_string.md) (equivalently, `#!cpp get<string_view_t>()`) returns a
|
|
||||||
string as a `string_view_t` pointing into the document's [`source()`](../api/basic_json_document/source.md) text —
|
|
||||||
or, for a string that contains escape sequences, into the document's own buffer of decoded strings — instead of
|
|
||||||
allocating a new `#!cpp std::string`.
|
|
||||||
- [`number_token()`](../api/basic_json_view/number_token.md) returns a number exactly as it was written in the
|
|
||||||
source, e.g. `#!cpp "1.50"`, `#!cpp "1E2"`, or an integer with more digits than any number type holds, instead of
|
|
||||||
rounding it into a `#!cpp double`/`#!cpp int64_t` the way `#!cpp get<T>()` (and
|
|
||||||
[`basic_json::parse()`](../api/basic_json/parse.md)) would.
|
|
||||||
|
|
||||||
Both results are only valid as long as the view — and, for a string with no escapes, the borrowed source text — is.
|
|
||||||
|
|
||||||
## Writing generic code
|
|
||||||
|
|
||||||
A view's type names its `BasicJsonType` and its [`view_mode`](../api/view_mode.md):
|
|
||||||
[`json_view`](../api/json_view.md) is `#!cpp basic_json_view<json, view_mode::read_only>`, and
|
|
||||||
[`json_editable_view`](../api/json_editable_view.md) is `#!cpp basic_json_view<json, view_mode::editable>`. These are
|
|
||||||
different types. The only conversion between them turns a view of a read-only document into a view of an editable
|
|
||||||
document of the same `BasicJsonType`, so a function written for one shortcut accepts few other views:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
void print_names(json_view v); // accepts neither an ordered_json_view nor a json_editable_view
|
|
||||||
void print_names(json_editable_view v); // also accepts a json_view, but no ordered_json_view
|
|
||||||
```
|
|
||||||
|
|
||||||
This is the same pitfall as a `to_json`/`from_json` written for `json` instead of any `BasicJsonType`, which then does
|
|
||||||
not work for `ordered_json`. The shortcuts are for declaring documents and views; a function that takes a view names
|
|
||||||
the views it accepts with a template:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// views of editable documents only
|
|
||||||
template<typename BasicJsonType>
|
|
||||||
void f(basic_json_view<BasicJsonType, view_mode::editable> v);
|
|
||||||
|
|
||||||
// views of read-only documents only
|
|
||||||
template<typename BasicJsonType>
|
|
||||||
void f(basic_json_view<BasicJsonType, view_mode::read_only> v);
|
|
||||||
|
|
||||||
// views of either document type
|
|
||||||
template<typename BasicJsonType, view_mode Mode>
|
|
||||||
void f(basic_json_view<BasicJsonType, Mode> v);
|
|
||||||
```
|
|
||||||
|
|
||||||
Views are passed by value: they are two pointers, cheap to copy, like a `#!cpp std::string_view`. A `const&` works as
|
|
||||||
well, but a non-`const` reference does not bind to the views that [`root()`](../api/basic_json_document/root.md),
|
|
||||||
[`operator[]`](../api/basic_json_view/operator%5B%5D.md), and [`at()`](../api/basic_json_view/at.md) return, so
|
|
||||||
`#!cpp f(doc.root())` would not compile.
|
|
||||||
|
|
||||||
There is no conversion from an editable view to a read-only one, by design: the mode decides how a view walks the
|
|
||||||
index. A read-only view reads a container in place, while a view of an editable document follows the links that edits
|
|
||||||
leave behind, so a read-only view of an edited document would read stale values. The other direction is safe: a
|
|
||||||
read-only document has no edits, so a view of it [converts](../api/basic_json_view/basic_json_view.md) to an
|
|
||||||
editable view, which reads it like an editable document before its first edit. A template deduces the mode without
|
|
||||||
conversions, so the template forms above still accept only the modes they name. Views of both modes compare with
|
|
||||||
[`operator==`](../api/basic_json_view/operator_eq.md).
|
|
||||||
|
|
||||||
[`is_json_view<T>`](../api/is_json_view.md) tells views from other types, for constraints that cannot be written as a
|
|
||||||
`basic_json_view<...>` parameter.
|
|
||||||
|
|
||||||
### A `from_json()` that reads views
|
|
||||||
|
|
||||||
[`get<T>()`](../api/basic_json_view/get.md) on a view calls `T`'s `from_json()` with the view itself if that
|
|
||||||
`from_json()` accepts views, and with [`materialize()`](../api/basic_json_view/materialize.md) otherwise. The
|
|
||||||
`from_json()` functions of the [`NLOHMANN_DEFINE_TYPE_*`](../api/macros/nlohmann_define_type_intrusive.md),
|
|
||||||
[`NLOHMANN_DEFINE_DERIVED_TYPE_*`](../api/macros/nlohmann_define_derived_type.md), and
|
|
||||||
[`NLOHMANN_JSON_SERIALIZE_ENUM`](../api/macros/nlohmann_json_serialize_enum.md) macros, and those of the library
|
|
||||||
for the standard types, accept views, so these types need nothing else. For decoding the statuses of
|
|
||||||
twitter.json into structs, reading the view is about 5 times faster than `#!cpp materialize().get<T>()`.
|
|
||||||
|
|
||||||
A hand-written `from_json()` reads views if it is constrained to `json` values and views:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
template < typename J, typename std::enable_if <
|
|
||||||
nlohmann::is_json_view<J>::value || std::is_same<J, nlohmann::json>::value, int >::type = 0 >
|
|
||||||
void from_json(const J& j, person& p)
|
|
||||||
{
|
|
||||||
p.name = j.at("name").template get<std::string>();
|
|
||||||
p.age = j.at("age").template get<int>();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Its body may only use what views and `basic_json` have in common: the type checks, `at()`, `operator[]`, `contains()`,
|
|
||||||
`value()`, `size()`, iteration and `items()`, and `get<T>()`/`get_to()`. An unconstrained
|
|
||||||
`#!cpp template<typename BasicJsonType> void from_json(const BasicJsonType& j, person& p)`, as in
|
|
||||||
[arbitrary type conversions](arbitrary_types.md), is never called with a view, as it might use `basic_json` functions
|
|
||||||
a view does not have; such a type is converted through `materialize()`. A `from_json()` for `json` only is
|
|
||||||
converted through `materialize()` as well.
|
|
||||||
|
|
||||||
Two things differ when a `from_json()` reads the view:
|
|
||||||
|
|
||||||
- **Duplicate keys.** Members looked up by name (`at()`, `value()`, ... and thus the macros) find the **first** member
|
|
||||||
with a repeated key, see [Duplicate keys](#duplicate-keys); `#!cpp materialize().get<T>()` would see the last one.
|
|
||||||
Containers built from an object, such as `#!cpp std::map`, keep the **last** value either way, as
|
|
||||||
[`parse()`](../api/basic_json/parse.md) does.
|
|
||||||
- **[`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md).** Exceptions thrown by the view carry no path to the
|
|
||||||
value.
|
|
||||||
|
|
||||||
`to_json()` is unaffected: it writes `basic_json` values, never views.
|
|
||||||
|
|
||||||
## Writing a view back
|
|
||||||
|
|
||||||
[`dump()`](../api/basic_json_view/dump.md) serializes a view directly from the flat index, without ever building a
|
|
||||||
`basic_json` value. An object's members are written in document order, not sorted by key, and *every* occurrence of a
|
|
||||||
repeated key is written, not only the last one — the same two ways [iteration](#what-is-different) already differs
|
|
||||||
from a [`materialize()`](../api/basic_json_view/materialize.md)d value, see [Duplicate keys](#duplicate-keys).
|
|
||||||
`#!cpp materialize().dump()` gives a different result in both respects for a `json_view`.
|
|
||||||
|
|
||||||
By default, numbers are written the way [`basic_json::dump()`](../api/basic_json/dump.md) would.
|
|
||||||
[`number_format::source`](../api/basic_json_view/number_format.md) instead copies every number exactly as it was
|
|
||||||
written in the source text — a price like `#!cpp 19.90`, a long order or account ID with more digits than any number
|
|
||||||
type holds, or a high-precision coordinate — something `basic_json` cannot do at all, since parsing already reduces
|
|
||||||
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, view_mode::editable>`](../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.
|
|
||||||
|
|
||||||
The [mode](../api/view_mode.md) defaults to `#!cpp view_mode::read_only`, 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.
|
|
||||||
|
|
||||||
The patch of the example below (set `"host"`, add `"retries"`) written back by each of them:
|
|
||||||
|
|
||||||
<div class="nj-jsonview-cmp" role="group" aria-label="The same patch written back by json, ordered_json, and json_editable_document">
|
|
||||||
<div><p class="nj-jsonview-cmp-h"><code>json</code></p><p class="nj-jsonview-cmp-v">keys sorted, numbers rewritten</p><pre>{<span class="nj-jsonview-bad">"host"</span>:"db2",
|
|
||||||
"name":"cache",
|
|
||||||
"port":6379,
|
|
||||||
"price":<span class="nj-jsonview-bad">19.9</span>,
|
|
||||||
"retries":3}</pre></div>
|
|
||||||
<div><p class="nj-jsonview-cmp-h"><code>ordered_json</code></p><p class="nj-jsonview-cmp-v">key order kept, numbers rewritten</p><pre>{"name":"cache",
|
|
||||||
"host":"db2",
|
|
||||||
"port":6379,
|
|
||||||
"price":<span class="nj-jsonview-bad">19.9</span>,
|
|
||||||
"retries":3}</pre></div>
|
|
||||||
<div class="nj-jsonview-cmp--best"><p class="nj-jsonview-cmp-h"><code>json_editable_document</code></p><p class="nj-jsonview-cmp-v">order and spelling kept</p><pre>{"name":"cache",
|
|
||||||
"host":"db2",
|
|
||||||
"port":6379,
|
|
||||||
"price":<span class="nj-jsonview-ok">19.90</span>,
|
|
||||||
"retries":3}</pre></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
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 an element is added to it,
|
|
||||||
inserted into it, erased from it, or replaced by a value that spans a different number of nodes, 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).
|
|
||||||
|
|
||||||
Step through the edits of a small document (the node fields follow the [architecture overview](../home/architecture.md#node-index-of-json-views)):
|
|
||||||
|
|
||||||
<div class="nj-jsonview-steps" data-nj-jsonview-steps>
|
|
||||||
<figure class="nj-jsonview-step" data-label="After parsing">
|
|
||||||
<div class="nj-jsonview-scroll">
|
|
||||||
--8<-- "images/json_view_edit_0.svg"
|
|
||||||
</div>
|
|
||||||
<figcaption>Step 1 of 5. After parsing: nine nodes in the parsed index, no owned storage yet.</figcaption>
|
|
||||||
</figure>
|
|
||||||
<figure class="nj-jsonview-step" data-label="set an existing member">
|
|
||||||
<div class="nj-jsonview-scroll">
|
|
||||||
--8<-- "images/json_view_edit_1.svg"
|
|
||||||
</div>
|
|
||||||
<figcaption>Step 2 of 5. <code>doc.set(doc.root(), "port", 6380)</code> overwrites the node of the integer in place; no member is added or removed, so the object keeps its parsed layout.</figcaption>
|
|
||||||
</figure>
|
|
||||||
<figure class="nj-jsonview-step" data-label="erase an array element">
|
|
||||||
<div class="nj-jsonview-scroll">
|
|
||||||
--8<-- "images/json_view_edit_2.svg"
|
|
||||||
</div>
|
|
||||||
<figcaption>Step 3 of 5. <code>doc.erase(doc.root()["tags"], 0)</code>: the array is edited for the first time, so its elements move to a block of links in owned storage. The node of <code>"eu"</code> stays in the index, but nothing refers to it any more; iterators over the array taken before are invalid.</figcaption>
|
|
||||||
</figure>
|
|
||||||
<figure class="nj-jsonview-step" data-label="push_back onto the array">
|
|
||||||
<div class="nj-jsonview-scroll">
|
|
||||||
--8<-- "images/json_view_edit_3.svg"
|
|
||||||
</div>
|
|
||||||
<figcaption>Step 4 of 5. <code>doc.push_back(doc.root()["tags"], "new")</code>: the new string gets its own node, its text goes to the buffer of edited text, and a link to it is appended to the block of the array.</figcaption>
|
|
||||||
</figure>
|
|
||||||
<figure class="nj-jsonview-step" data-label="set a new member">
|
|
||||||
<div class="nj-jsonview-scroll">
|
|
||||||
--8<-- "images/json_view_edit_4.svg"
|
|
||||||
</div>
|
|
||||||
<figcaption>Step 5 of 5. <code>doc.set(doc.root(), "retries", 3)</code>: a new member, so the object moves to a block too: copies of its keys, links to the values it already had, then the new key and a link to the new integer.</figcaption>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="nj-caption" markdown>The node index and the owned storage of the document after each of five edits. Use *Back*, *Next*, or the numbered buttons to switch between the steps; without JavaScript all steps are shown one below the other.</p>
|
|
||||||
|
|
||||||
## 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 (on big-endian targets, `save()` and `load()` do not exist), 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) | [`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 |
|
|
||||||
|
|
||||||
<p class="nj-table-caption" markdown>Each column is one way to work with JSON in this library, and each row compares one property of them.</p>
|
|
||||||
|
|
||||||
## See also
|
|
||||||
|
|
||||||
- [`basic_json_document`](../api/basic_json_document/index.md) - the document that owns the parsed index (parse, access, images, edits)
|
|
||||||
- [`basic_json_view`](../api/basic_json_view/index.md) - the non-owning handle to a value in a document
|
|
||||||
- [`json_document`](../api/json_document.md) - and [`ordered_json_document`](../api/ordered_json_document.md) - the read-only documents
|
|
||||||
- [`json_editable_document`](../api/json_editable_document.md) - and [`ordered_json_editable_document`](../api/ordered_json_editable_document.md) - the documents that can be edited in place
|
|
||||||
- [Architecture](../home/architecture.md#node-index-of-json-views) - the layout of the node index
|
|
||||||
- [SAX interface](parsing/sax_interface.md) - event-based parsing without building a tree
|
|
||||||
- [Performance](performance.md) - benchmarks and tuning of parsing and serialization
|
|
||||||
- [Large, Streamed, and Partial Input](parsing/large_and_partial_input.md) - reading only the part of a text you need
|
|
||||||
|
|
||||||
## Version history
|
|
||||||
|
|
||||||
- Added in version 3.13.0.
|
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
# Differences from `basic_json`
|
||||||
|
|
||||||
|
A [`basic_json_view`](../../api/basic_json_view/index.md) offers the read-only part of the
|
||||||
|
[`basic_json`](../../api/basic_json/index.md) interface, and in most respects it behaves exactly like the value
|
||||||
|
[`basic_json::parse()`](../../api/basic_json/parse.md) would have built from the same text. This page lists where it
|
||||||
|
does not: what a document parses differently, what a view's functions do differently, and which parts of the
|
||||||
|
`basic_json` interface a view does not have.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| | `basic_json` value from `parse()` | view of a `json_document` | to get the `basic_json` behavior |
|
||||||
|
|---|---|---|---|
|
||||||
|
| [Integer types](#parsing) | any `number_integer_t`/`number_unsigned_t` | 64-bit only (compile-time check) | — |
|
||||||
|
| [Input size](#parsing) | unlimited | less than 4 GiB | — |
|
||||||
|
| [Parser callbacks](#parsing) | `parser_callback_t` | none | filter while iterating |
|
||||||
|
| [`operator[]` on a missing key or index](#element-access) | inserts (non-`const`) or undefined behavior (`const`) | returns a discarded view | — |
|
||||||
|
| [Object member order](#object-member-order) | sorted by key (`json`), insertion order (`ordered_json`) | document order | `materialize()` |
|
||||||
|
| [Repeated keys](#duplicate-keys) | the last value is kept | lookups find the first member; iteration, `size()`, and `dump()` see all of them | `materialize()` |
|
||||||
|
| [Conversions](#conversions) | `get<T>()`, implicit conversions, `get_ptr()`, `get_ref()` | `get<T>()`, `get_string()`, `number_token()` | `materialize()` |
|
||||||
|
| [Number spelling](#serialization) | lost when parsing | kept: `number_token()`, `number_format::source` | — |
|
||||||
|
| [Comparison](#comparison) | `==`, `!=`, `<`, `<=>`, ... | `==` and `!=` only | `materialize()` |
|
||||||
|
| [Modification](#what-a-view-does-not-have) | member functions of the value | only through an [editable document](editing.md) | `materialize()` |
|
||||||
|
| [Iterators](#what-a-view-does-not-have) | bidirectional, reverse iterators | forward only | `materialize()` |
|
||||||
|
|
||||||
|
<p class="nj-table-caption" markdown>Each row is one difference; the last column names the way to get `basic_json`'s behavior from a view, where there is one.</p>
|
||||||
|
|
||||||
|
## Parsing
|
||||||
|
|
||||||
|
### What is the same
|
||||||
|
|
||||||
|
- **Accept/reject.** [`accept()`](../../api/basic_json_document/accept.md) and
|
||||||
|
[`parse()`](../../api/basic_json_document/parse.md) accept and reject exactly the same inputs as
|
||||||
|
[`basic_json::accept()`](../../api/basic_json/accept.md)/[`basic_json::parse()`](../../api/basic_json/parse.md),
|
||||||
|
with the same `ignore_comments` and `ignore_trailing_commas` options.
|
||||||
|
- **Errors.** A failing parse throws the same exception — the same id, message, and position — because on failure
|
||||||
|
the library's own parser is run on the same bytes to produce the diagnostic. Valid input never pays for this, and
|
||||||
|
the replay builds no values: it costs about a lexing pass up to the error.
|
||||||
|
`#!cpp allow_exceptions == false` skips the replay and gives a
|
||||||
|
[discarded](../../api/basic_json_document/is_discarded.md) document instead of throwing, just as it gives a
|
||||||
|
discarded value for `#!cpp basic_json::parse()`.
|
||||||
|
- **Number classification.** An integer literal that does not fit into the 64-bit integer type becomes a
|
||||||
|
floating-point value, exactly as it does for `#!cpp basic_json::parse()`.
|
||||||
|
- **Macros.** [`JSON_STRICT_NUL_HANDLING`](../../api/macros/json_strict_nul_handling.md) and
|
||||||
|
[`JSON_NOEXCEPTION`](../../api/macros/json_noexception.md)/[`JSON_THROW_USER`](../../api/macros/json_throw_user.md)
|
||||||
|
behave the same way they do for `<nlohmann/json.hpp>`. These are the macros that change what the parser accepts or
|
||||||
|
how it reports errors. The other macros only affect the values `materialize()` produces, through `BasicJsonType`.
|
||||||
|
Two of them also add context to the exceptions of views, see [Exception context](#exception-context).
|
||||||
|
|
||||||
|
### What is different
|
||||||
|
|
||||||
|
- **Only 64-bit integers.** `basic_json_document<BasicJsonType>` requires `BasicJsonType::number_integer_t` and
|
||||||
|
`number_unsigned_t` to both be 64 bits wide; this is a compile-time `#!cpp static_assert`.
|
||||||
|
- **A 4 GiB input limit.** An input of 4294967280 bytes (4 GiB minus 16 bytes) or more throws
|
||||||
|
[`out_of_range.416`](../../home/exceptions.md#jsonexceptionout_of_range416), a limit
|
||||||
|
`#!cpp basic_json::parse()` does not have. It keeps an index entry at 16 bytes: offsets and counts are 32 bits
|
||||||
|
wide. The limit is 16 bytes below 2^32, because the parser reads in 16-byte steps, and a position one step past the
|
||||||
|
end must still fit.
|
||||||
|
- **A stream is always read to its end.** There is no partial/streaming read of an `#!cpp std::istream`: the stream
|
||||||
|
is read to its end into a buffer the document owns, and parsing starts after that.
|
||||||
|
- **No parser callbacks.** [`parse()`](../../api/basic_json_document/parse.md) takes no
|
||||||
|
[`parser_callback_t`](../../api/basic_json/parser_callback_t.md): a document always indexes the whole text. Skip
|
||||||
|
the values you do not need when reading the view instead.
|
||||||
|
- **JSON text only.** A document is parsed from JSON text; there is no counterpart of
|
||||||
|
[`from_cbor()`](../../api/basic_json/from_cbor.md) and the other binary formats, and
|
||||||
|
[`is_binary()`](../../api/basic_json_view/is_binary.md) is always `#!cpp false`.
|
||||||
|
|
||||||
|
## Element access
|
||||||
|
|
||||||
|
**Chained access is safe.** [`operator[]`](../../api/basic_json_view/operator%5B%5D.md) with a missing key, an index
|
||||||
|
out of range, or an unresolvable JSON pointer returns a [discarded](../../api/basic_json_view/is_discarded.md) view,
|
||||||
|
and `operator[]` on a discarded view returns a discarded view without throwing: `#!cpp v["a"]["b"][0]` can be tested
|
||||||
|
once at the end. Type errors on values that exist (a key on an array, an index on an object) still throw, and
|
||||||
|
[`at`](../../api/basic_json_view/at.md) throws for every missing value.
|
||||||
|
|
||||||
|
`basic_json`'s non-`const` `operator[]` instead inserts a missing key, and its `const` `operator[]` has undefined
|
||||||
|
behavior for one.
|
||||||
|
|
||||||
|
## Objects
|
||||||
|
|
||||||
|
### Object member order
|
||||||
|
|
||||||
|
[`begin()`](../../api/basic_json_view/begin.md)/[`end()`](../../api/basic_json_view/end.md) and
|
||||||
|
[`items()`](../../api/basic_json_view/items.md) visit an object's members in the order they appear in the source text.
|
||||||
|
`basic_json`'s default `object_t` is a `std::map`, which sorts by key, so iterating a
|
||||||
|
[`materialize()`](../../api/basic_json_view/materialize.md)d value can print members in a different order than
|
||||||
|
iterating the view they came from. [`dump()`](../../api/basic_json_view/dump.md) of a view writes members in document
|
||||||
|
order as well.
|
||||||
|
|
||||||
|
### Duplicate keys
|
||||||
|
|
||||||
|
!!! warning "A lookup in a view and in the parsed value can give different answers"
|
||||||
|
|
||||||
|
If an object in the source text repeats a key, [`operator[]`](../../api/basic_json_view/operator%5B%5D.md),
|
||||||
|
[`at`](../../api/basic_json_view/at.md), [`find`](../../api/basic_json_view/find.md),
|
||||||
|
[`value`](../../api/basic_json_view/value.md), [`contains`](../../api/basic_json_view/contains.md), and JSON
|
||||||
|
pointer resolution all return the **first** member with that key. `basic_json::parse()` instead keeps the **last**
|
||||||
|
value of a repeated key, so for `#!cpp {"a":1,"a":2}`, `#!cpp view["a"]` is `#!cpp 1` while
|
||||||
|
`#!cpp parse(text)["a"]` is `#!cpp 2`. [`count`](../../api/basic_json_view/count.md) counts every member with the
|
||||||
|
key, so it is `#!cpp 2` here.
|
||||||
|
|
||||||
|
[`begin()`](../../api/basic_json_view/begin.md)/[`end()`](../../api/basic_json_view/end.md) and
|
||||||
|
[`items()`](../../api/basic_json_view/items.md) visit *every* occurrence, in document order, and
|
||||||
|
[`size()`](../../api/basic_json_view/size.md) counts all of them, so `#!cpp view.size()` can be larger than
|
||||||
|
`#!cpp view.materialize().size()`. [`dump()`](../../api/basic_json_view/dump.md) writes every occurrence, too, so
|
||||||
|
`#!cpp materialize().dump()` gives a different result for such an object.
|
||||||
|
|
||||||
|
**Why the first?** A lookup can stop as soon as it finds a match. Returning the last member would force every lookup
|
||||||
|
to scan all members of the object, even when the key is found at the very first one: this made lookups in small
|
||||||
|
objects 1.6 to 3.4 times slower. The hash index of the keys of objects with 128 members or more leads to the first
|
||||||
|
member of a key as well. Other zero-copy parsers that index the source text, such as yyjson and simdjson, also return
|
||||||
|
the first member. RFC 8259 only says that names within an object SHOULD be unique and that the behavior of a receiver
|
||||||
|
that sees duplicates is unpredictable, so neither choice is wrong.
|
||||||
|
|
||||||
|
The same holds for [`get<T>()`](../../api/basic_json_view/get.md) of a type whose `from_json()` reads the view, such
|
||||||
|
as the types of the `NLOHMANN_DEFINE_TYPE_*` macros: their members are looked up, so `#!cpp get<point>()` of
|
||||||
|
`#!cpp {"x":1,"x":2}` reads `#!cpp x == 1`, while `#!cpp materialize().get<point>()` reads `#!cpp 2`.
|
||||||
|
|
||||||
|
**What stays the same as `parse()`?** [`materialize()`](../../api/basic_json_view/materialize.md) and
|
||||||
|
[`get<std::map<...>>()`](../../api/basic_json_view/get.md) (and the other containers built from an object) replay
|
||||||
|
every member in order, so they keep the *last* value exactly like `#!cpp basic_json::parse()` (at the position of the
|
||||||
|
first occurrence of the key, for an [`ordered_json`](../../api/ordered_json.md)).
|
||||||
|
|
||||||
|
**How do I get the value `parse()` would give?** Either call `#!cpp view.materialize()` and look the key up in the
|
||||||
|
result, or iterate the members and keep the last match:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// the last member with the key "a", as parse() would keep it
|
||||||
|
json_view last;
|
||||||
|
for (const auto item : view.items())
|
||||||
|
{
|
||||||
|
if (item.key() == "a")
|
||||||
|
{
|
||||||
|
last = item.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you cannot trust the source text to have unique keys, check [`size()`](../../api/basic_json_view/size.md) against
|
||||||
|
the number of distinct keys, or reject duplicates when iterating.
|
||||||
|
|
||||||
|
## Conversions
|
||||||
|
|
||||||
|
- **No implicit conversions.** A view has no `#!cpp operator ValueType()`, no `#!cpp get_ptr()`, and no
|
||||||
|
`#!cpp get_ref()`; use [`get<T>()`](../../api/basic_json_view/get.md), or
|
||||||
|
[`get_string()`](../../api/basic_json_view/get_string.md) for a string without a copy.
|
||||||
|
- **`from_json()` overloads.** [`get<T>()`](../../api/basic_json_view/get.md) calls `T`'s `from_json()` with the view
|
||||||
|
only if that `from_json()` accepts views, and with [`materialize()`](../../api/basic_json_view/materialize.md)
|
||||||
|
otherwise; see [A `from_json()` that reads views](generic_code.md#a-from_json-that-reads-views). Both give the same
|
||||||
|
result except for [repeated keys](#duplicate-keys).
|
||||||
|
- **`string_view` members.** A struct with a `#!cpp std::string_view` member decoded from a view points into the
|
||||||
|
document, see [`string_view` members](reading.md#string_view-members). Decoding the same struct from a `json` value
|
||||||
|
compiles, too, but dangles at once if the value is a temporary.
|
||||||
|
|
||||||
|
## Serialization
|
||||||
|
|
||||||
|
[`dump()`](../../api/basic_json_view/dump.md) writes numbers the way
|
||||||
|
[`basic_json::dump()`](../../api/basic_json/dump.md) would by default, but writes object members in
|
||||||
|
[document order](#object-member-order) and writes [every occurrence of a repeated key](#duplicate-keys).
|
||||||
|
|
||||||
|
A view can also do what a `basic_json` value cannot: [`number_token()`](../../api/basic_json_view/number_token.md)
|
||||||
|
and [`number_format::source`](../../api/basic_json_view/number_format.md) give every number exactly as it was spelled
|
||||||
|
in the source text, while parsing into `basic_json` already reduces a number to its `#!cpp double`/`#!cpp int64_t`
|
||||||
|
value. See [Writing a view back](reading.md#writing-a-view-back).
|
||||||
|
|
||||||
|
## Comparison
|
||||||
|
|
||||||
|
**Ordering comparisons are not provided** by `basic_json_view` — there is no `#!cpp operator<`.
|
||||||
|
[`operator==`](../../api/basic_json_view/operator_eq.md) and [`operator!=`](../../api/basic_json_view/operator_ne.md)
|
||||||
|
are provided, though: two views, or a view and a `BasicJsonType` value, compare equal exactly when
|
||||||
|
[`materialize()`](../../api/basic_json_view/materialize.md) or [`parse()`](../../api/basic_json/parse.md) would produce
|
||||||
|
equal values for them, without ever building a tree to do it. For ordering,
|
||||||
|
[`materialize()`](../../api/basic_json_view/materialize.md) is the way to get a value you can compare.
|
||||||
|
|
||||||
|
## Exception context
|
||||||
|
|
||||||
|
With [`JSON_DIAGNOSTICS`](../../api/macros/json_diagnostics.md), an exception of a view names the JSON Pointer path of
|
||||||
|
the value within the document, and with
|
||||||
|
[`JSON_DIAGNOSTIC_POSITIONS`](../../api/macros/json_diagnostic_positions.md), it names the bytes of the value in the
|
||||||
|
source text, as they do for the exceptions of `#!cpp basic_json`.
|
||||||
|
|
||||||
|
A view has neither parent pointers nor stored positions, so both are found from the document's index when the
|
||||||
|
exception is thrown. This takes time linear in the size of the document. One exception has neither: `key()` of an
|
||||||
|
iterator over an array or a primitive value
|
||||||
|
([`invalid_iterator.207`](../../home/exceptions.md#jsonexceptioninvalid_iterator207)), because a view's iterator does
|
||||||
|
not know the value it iterates over.
|
||||||
|
|
||||||
|
**Fewer source positions after edits.** With
|
||||||
|
[`JSON_DIAGNOSTIC_POSITIONS`](../../api/macros/json_diagnostic_positions.md) enabled,
|
||||||
|
[`materialize()`](../../api/basic_json_view/materialize.md) sets the same positions as `#!cpp basic_json::parse()`. In
|
||||||
|
an [editable document](editing.md), a value written by an edit has none, and neither has an array or object whose
|
||||||
|
elements changed or whose last element has none; a `basic_json` value keeps its positions when it is changed. See
|
||||||
|
[`materialize()`](../../api/basic_json_view/materialize.md#notes).
|
||||||
|
|
||||||
|
## What a view does not have
|
||||||
|
|
||||||
|
A view is a read-only handle. These parts of the `basic_json` interface have no counterpart on a view;
|
||||||
|
[`materialize()`](../../api/basic_json_view/materialize.md) the subtree to use them:
|
||||||
|
|
||||||
|
- **Modifiers** such as `push_back`, `insert`, `erase`, `update`, or `operator=`. A view never changes a value
|
||||||
|
itself; an [editable document](editing.md) changes the values its views refer to through
|
||||||
|
[`set`](../../api/basic_json_document/set.md), [`push_back`](../../api/basic_json_document/push_back.md),
|
||||||
|
[`insert`](../../api/basic_json_document/insert.md), and [`erase`](../../api/basic_json_document/erase.md).
|
||||||
|
- **Reverse iteration.** A view's iterator is a forward iterator; there is no `rbegin()`/`rend()`.
|
||||||
|
- **Ordering comparisons**, see [Comparison](#comparison).
|
||||||
|
- **JSON Patch and friends**: [`patch()`](../../api/basic_json/patch.md),
|
||||||
|
[`merge_patch()`](../../api/basic_json/merge_patch.md), [`diff()`](../../api/basic_json/diff.md),
|
||||||
|
[`flatten()`](../../api/basic_json/flatten.md).
|
||||||
|
- **Binary formats**: [`to_cbor()`](../../api/basic_json/to_cbor.md) and the other writers, and binary values
|
||||||
|
([`get_binary()`](../../api/basic_json/get_binary.md)).
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [Zero-copy JSON views](index.md) - overview
|
||||||
|
- [`basic_json_view`](../../api/basic_json_view/index.md) - the full interface of a view
|
||||||
|
- [`materialize()`](../../api/basic_json_view/materialize.md) - build the `basic_json` value of a view
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
# Editing a document
|
||||||
|
|
||||||
|
A [`json_document`](../../api/json_document.md)/[`json_view`](../../api/json_view.md) lets you look at a parsed text
|
||||||
|
without copying it, but not change it.
|
||||||
|
[`basic_json_document<BasicJsonType, view_mode::editable>`](../../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.
|
||||||
|
|
||||||
|
The [mode](../../api/view_mode.md) defaults to `#!cpp view_mode::read_only`, 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
|
||||||
|
|
||||||
|
A price like `#!cpp 19.90` 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.
|
||||||
|
|
||||||
|
The patch of the example below (set `"host"`, add `"retries"`) written back by each of them:
|
||||||
|
|
||||||
|
<div class="nj-jsonview-cmp" role="group" aria-label="The same patch written back by json, ordered_json, and json_editable_document">
|
||||||
|
<div><p class="nj-jsonview-cmp-h"><code>json</code></p><p class="nj-jsonview-cmp-v">keys sorted, numbers rewritten</p><pre>{<span class="nj-jsonview-bad">"host"</span>:"db2",
|
||||||
|
"name":"cache",
|
||||||
|
"port":6379,
|
||||||
|
"price":<span class="nj-jsonview-bad">19.9</span>,
|
||||||
|
"retries":3}</pre></div>
|
||||||
|
<div><p class="nj-jsonview-cmp-h"><code>ordered_json</code></p><p class="nj-jsonview-cmp-v">key order kept, numbers rewritten</p><pre>{"name":"cache",
|
||||||
|
"host":"db2",
|
||||||
|
"port":6379,
|
||||||
|
"price":<span class="nj-jsonview-bad">19.9</span>,
|
||||||
|
"retries":3}</pre></div>
|
||||||
|
<div class="nj-jsonview-cmp--best"><p class="nj-jsonview-cmp-h"><code>json_editable_document</code></p><p class="nj-jsonview-cmp-v">order and spelling kept</p><pre>{"name":"cache",
|
||||||
|
"host":"db2",
|
||||||
|
"port":6379,
|
||||||
|
"price":<span class="nj-jsonview-ok">19.90</span>,
|
||||||
|
"retries":3}</pre></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
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. See [Writing a view back](reading.md#writing-a-view-back).
|
||||||
|
|
||||||
|
??? 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 an element is added to it,
|
||||||
|
inserted into it, erased from it, or replaced by a value that spans a different number of nodes, 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).
|
||||||
|
|
||||||
|
Step through the edits of a small document (the node fields follow the
|
||||||
|
[architecture overview](../../home/architecture.md#node-index-of-json-views)):
|
||||||
|
|
||||||
|
<div class="nj-jsonview-steps" data-nj-jsonview-steps>
|
||||||
|
<figure class="nj-jsonview-step" data-label="After parsing">
|
||||||
|
<div class="nj-jsonview-scroll">
|
||||||
|
--8<-- "images/json_view_edit_0.svg"
|
||||||
|
</div>
|
||||||
|
<figcaption>Step 1 of 5. After parsing: nine nodes in the parsed index, no owned storage yet.</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure class="nj-jsonview-step" data-label="set an existing member">
|
||||||
|
<div class="nj-jsonview-scroll">
|
||||||
|
--8<-- "images/json_view_edit_1.svg"
|
||||||
|
</div>
|
||||||
|
<figcaption>Step 2 of 5. <code>doc.set(doc.root(), "port", 6380)</code> overwrites the node of the integer in place; no member is added or removed, so the object keeps its parsed layout.</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure class="nj-jsonview-step" data-label="erase an array element">
|
||||||
|
<div class="nj-jsonview-scroll">
|
||||||
|
--8<-- "images/json_view_edit_2.svg"
|
||||||
|
</div>
|
||||||
|
<figcaption>Step 3 of 5. <code>doc.erase(doc.root()["tags"], 0)</code>: the array is edited for the first time, so its elements move to a block of links in owned storage. The node of <code>"eu"</code> stays in the index, but nothing refers to it any more; iterators over the array taken before are invalid.</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure class="nj-jsonview-step" data-label="push_back onto the array">
|
||||||
|
<div class="nj-jsonview-scroll">
|
||||||
|
--8<-- "images/json_view_edit_3.svg"
|
||||||
|
</div>
|
||||||
|
<figcaption>Step 4 of 5. <code>doc.push_back(doc.root()["tags"], "new")</code>: the new string gets its own node, its text goes to the buffer of edited text, and a link to it is appended to the block of the array.</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure class="nj-jsonview-step" data-label="set a new member">
|
||||||
|
<div class="nj-jsonview-scroll">
|
||||||
|
--8<-- "images/json_view_edit_4.svg"
|
||||||
|
</div>
|
||||||
|
<figcaption>Step 5 of 5. <code>doc.set(doc.root(), "retries", 3)</code>: a new member, so the object moves to a block too: copies of its keys, links to the values it already had, then the new key and a link to the new integer.</figcaption>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="nj-caption" markdown>The node index and the owned storage of the document after each of five edits. Use *Back*, *Next*, or the numbered buttons to switch between the steps; without JavaScript all steps are shown one below the other.</p>
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [Zero-copy JSON views](index.md) - overview
|
||||||
|
- [`json_editable_document`](../../api/json_editable_document.md) - and [`ordered_json_editable_document`](../../api/ordered_json_editable_document.md) - the documents that can be edited in place
|
||||||
|
- [`basic_json_document`'s Edits](../../api/basic_json_document/index.md#edits) - the rules of edits in detail
|
||||||
|
- [Differences from `basic_json`](differences.md#exception-context) - source positions after edits
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# Writing generic code
|
||||||
|
|
||||||
|
## View types
|
||||||
|
|
||||||
|
A view's type names its `BasicJsonType` and its [`view_mode`](../../api/view_mode.md):
|
||||||
|
[`json_view`](../../api/json_view.md) is `#!cpp basic_json_view<json, view_mode::read_only>`, and
|
||||||
|
[`json_editable_view`](../../api/json_editable_view.md) is `#!cpp basic_json_view<json, view_mode::editable>`. These
|
||||||
|
are different types. The only conversion between them turns a view of a read-only document into a view of an
|
||||||
|
editable document of the same `BasicJsonType`, so a function written for one shortcut accepts few other views:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void print_names(json_view v); // accepts neither an ordered_json_view nor a json_editable_view
|
||||||
|
void print_names(json_editable_view v); // also accepts a json_view, but no ordered_json_view
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the same pitfall as a `to_json`/`from_json` written for `json` instead of any `BasicJsonType`, which then does
|
||||||
|
not work for `ordered_json`. The shortcuts are for declaring documents and views; a function that takes a view names
|
||||||
|
the views it accepts with a template:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// views of editable documents only
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
void f(basic_json_view<BasicJsonType, view_mode::editable> v);
|
||||||
|
|
||||||
|
// views of read-only documents only
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
void f(basic_json_view<BasicJsonType, view_mode::read_only> v);
|
||||||
|
|
||||||
|
// views of either document type
|
||||||
|
template<typename BasicJsonType, view_mode Mode>
|
||||||
|
void f(basic_json_view<BasicJsonType, Mode> v);
|
||||||
|
```
|
||||||
|
|
||||||
|
Views are passed by value: they are two pointers, cheap to copy, like a `#!cpp std::string_view`. A `const&` works as
|
||||||
|
well, but a non-`const` reference does not bind to the views that
|
||||||
|
[`root()`](../../api/basic_json_document/root.md), [`operator[]`](../../api/basic_json_view/operator%5B%5D.md), and
|
||||||
|
[`at()`](../../api/basic_json_view/at.md) return, so `#!cpp f(doc.root())` would not compile.
|
||||||
|
|
||||||
|
There is no conversion from an editable view to a read-only one, by design: the mode decides how a view walks the
|
||||||
|
index. A read-only view reads a container in place, while a view of an editable document follows the links that edits
|
||||||
|
leave behind, so a read-only view of an edited document would read stale values. The other direction is safe: a
|
||||||
|
read-only document has no edits, so a view of it [converts](../../api/basic_json_view/basic_json_view.md) to an
|
||||||
|
editable view, which reads it like an editable document before its first edit. A template deduces the mode without
|
||||||
|
conversions, so the template forms above still accept only the modes they name. Views of both modes compare with
|
||||||
|
[`operator==`](../../api/basic_json_view/operator_eq.md).
|
||||||
|
|
||||||
|
[`is_json_view<T>`](../../api/is_json_view.md) tells views from other types, for constraints that cannot be written as
|
||||||
|
a `basic_json_view<...>` parameter.
|
||||||
|
|
||||||
|
## A `from_json()` that reads views
|
||||||
|
|
||||||
|
[`get<T>()`](../../api/basic_json_view/get.md) on a view calls `T`'s `from_json()` with the view itself if that
|
||||||
|
`from_json()` accepts views, and with [`materialize()`](../../api/basic_json_view/materialize.md) otherwise. The
|
||||||
|
`from_json()` functions of the [`NLOHMANN_DEFINE_TYPE_*`](../../api/macros/nlohmann_define_type_intrusive.md),
|
||||||
|
[`NLOHMANN_DEFINE_DERIVED_TYPE_*`](../../api/macros/nlohmann_define_derived_type.md), and
|
||||||
|
[`NLOHMANN_JSON_SERIALIZE_ENUM`](../../api/macros/nlohmann_json_serialize_enum.md) macros, and those of the library
|
||||||
|
for the standard types, accept views, so these types need nothing else. For decoding the statuses of
|
||||||
|
twitter.json into structs, reading the view is about 5 times faster than `#!cpp materialize().get<T>()`.
|
||||||
|
|
||||||
|
A hand-written `from_json()` reads views if it is constrained to `json` values and views:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template < typename J, typename std::enable_if <
|
||||||
|
nlohmann::is_json_view<J>::value || std::is_same<J, nlohmann::json>::value, int >::type = 0 >
|
||||||
|
void from_json(const J& j, person& p)
|
||||||
|
{
|
||||||
|
p.name = j.at("name").template get<std::string>();
|
||||||
|
p.age = j.at("age").template get<int>();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Its body may only use what views and `basic_json` have in common: the type checks, `at()`, `operator[]`,
|
||||||
|
`contains()`, `value()`, `size()`, iteration and `items()`, and `get<T>()`/`get_to()`. An unconstrained
|
||||||
|
`#!cpp template<typename BasicJsonType> void from_json(const BasicJsonType& j, person& p)`, as in
|
||||||
|
[arbitrary type conversions](../arbitrary_types.md), is never called with a view, as it might use `basic_json`
|
||||||
|
functions a view does not have; such a type is converted through `materialize()`. A `from_json()` for `json` only is
|
||||||
|
converted through `materialize()` as well.
|
||||||
|
|
||||||
|
One thing differs when a `from_json()` reads the view: members looked up by name (`at()`, `value()`, ... and thus
|
||||||
|
the macros) find the **first** member with a repeated key, see [Duplicate keys](differences.md#duplicate-keys);
|
||||||
|
`#!cpp materialize().get<T>()` would see the last one. Containers built from an object, such as `#!cpp std::map`,
|
||||||
|
keep the **last** value either way, as [`parse()`](../../api/basic_json/parse.md) does.
|
||||||
|
|
||||||
|
`to_json()` is unaffected: it writes `basic_json` values, never views.
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [Zero-copy JSON views](index.md) - overview
|
||||||
|
- [`is_json_view`](../../api/is_json_view.md) - type trait to detect views
|
||||||
|
- [`view_mode`](../../api/view_mode.md) - read-only and editable views
|
||||||
|
- [Reading values](reading.md) - what `get<T>()` converts without materializing
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# 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 (on big-endian targets, `save()` and `load()` do not exist), 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.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [Zero-copy JSON views](index.md) - overview
|
||||||
|
- [`save()`](../../api/basic_json_document/save.md) - write a document as an image
|
||||||
|
- [`load()`](../../api/basic_json_document/load.md) - read an image back into a document
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# Zero-copy JSON views
|
||||||
|
|
||||||
|
`#!cpp <nlohmann/json_view.hpp>` adds a read-only, non-owning way to look at a parsed JSON text, as an alternative to
|
||||||
|
building a [`basic_json`](../../api/basic_json/index.md) tree with [`parse()`](../../api/basic_json/parse.md).
|
||||||
|
|
||||||
|
## The problem
|
||||||
|
|
||||||
|
[`basic_json::parse()`](../../api/basic_json/parse.md) builds a tree of `basic_json` values: one allocation for every
|
||||||
|
array and object, and every string copied into its own `std::string`. That is the right trade-off when the program
|
||||||
|
goes on to read and write the value freely, but it does more work than necessary when only a small part of a large
|
||||||
|
JSON text is actually needed, or when the same text is parsed over and over (many small messages, for instance) and
|
||||||
|
most of the resulting tree is thrown away almost immediately.
|
||||||
|
|
||||||
|
## The idea
|
||||||
|
|
||||||
|
[`basic_json_document::parse()`](../../api/basic_json_document/parse.md) parses the same JSON grammar, with the same
|
||||||
|
options, but instead of a tree it builds a flat index of the values it found: one
|
||||||
|
[16-byte entry](../../home/architecture.md#node-index-of-json-views) per value (and one per object key), in document
|
||||||
|
order. Integers are converted while parsing and stored in their entry. Floating-point numbers and strings are
|
||||||
|
referenced in place in the source text: a float is converted when it is read, and only strings that contain escape
|
||||||
|
sequences are decoded, into one shared buffer owned by the document.
|
||||||
|
|
||||||
|
[`basic_json_view`](../../api/basic_json_view/index.md) is a small, trivially copyable handle (two pointers) into that
|
||||||
|
index. It gives you the read-only, type-inspection part of the `basic_json` interface —
|
||||||
|
[`type()`](../../api/basic_json_view/type.md) and the `is_*()` predicates,
|
||||||
|
[`size()`](../../api/basic_json_view/size.md)/[`empty()`](../../api/basic_json_view/empty.md) — as well as element
|
||||||
|
access ([`operator[]`](../../api/basic_json_view/operator%5B%5D.md), [`at`](../../api/basic_json_view/at.md),
|
||||||
|
[`front`](../../api/basic_json_view/front.md)/[`back`](../../api/basic_json_view/back.md)), lookup
|
||||||
|
([`find`](../../api/basic_json_view/find.md), [`contains`](../../api/basic_json_view/contains.md),
|
||||||
|
[`count`](../../api/basic_json_view/count.md)), and iteration
|
||||||
|
([`begin`](../../api/basic_json_view/begin.md)/[`end`](../../api/basic_json_view/end.md),
|
||||||
|
[`items`](../../api/basic_json_view/items.md)) — without ever allocating a `basic_json` value. When you do need an
|
||||||
|
actual `basic_json` value for a subtree, [`materialize()`](../../api/basic_json_view/materialize.md) builds exactly the
|
||||||
|
one [`parse()`](../../api/basic_json/parse.md) would have produced for it.
|
||||||
|
|
||||||
|
The same small text, parsed both ways:
|
||||||
|
|
||||||
|
<div class="nj-fig-wrap" markdown>
|
||||||
|
--8<-- "images/json_view_layout.svg"
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="nj-caption" markdown>Top: the tree of separate heap allocations that `basic_json::parse()` builds. Bottom: `json_document::parse()` keeps the source text and builds one flat index of 16-byte nodes that point back into it, plus a buffer of decoded strings.</p>
|
||||||
|
|
||||||
|
## How to use it
|
||||||
|
|
||||||
|
Include `<nlohmann/json_view.hpp>` in addition to (or instead of) `<nlohmann/json.hpp>`. Parse into a
|
||||||
|
[`json_document`](../../api/json_document.md), inspect its [`root()`](../../api/basic_json_document/root.md), and
|
||||||
|
`materialize()` when you need a real value. `materialize()` works on any view, not just the root, so you can turn just
|
||||||
|
the subtree you need into a value:
|
||||||
|
|
||||||
|
??? example "Example: parse a document, inspect its root, and materialize it"
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
--8<-- "examples/json_document.cpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
--8<-- "examples/json_document.output"
|
||||||
|
```
|
||||||
|
|
||||||
|
[`ordered_json_document`](../../api/ordered_json_document.md) is the equivalent for
|
||||||
|
[`ordered_json`](../../api/ordered_json.md), just as [`ordered_json`](../../api/ordered_json.md) is to
|
||||||
|
[`json`](../../api/json.md).
|
||||||
|
|
||||||
|
A view behaves like a `basic_json` value in most respects, but not in all: lookups of a repeated key, the order in
|
||||||
|
which an object's members are visited, and what a missing key does to chained `operator[]` are the most visible
|
||||||
|
differences. [Differences from `basic_json`](differences.md) lists all of them.
|
||||||
|
|
||||||
|
## Where to go next
|
||||||
|
|
||||||
|
- [Ownership and lifetime](lifetime.md) - when a document borrows its source text, and how long a view stays valid
|
||||||
|
- [Differences from `basic_json`](differences.md) - everything a view does differently from a parsed `basic_json` value
|
||||||
|
- [Reading values](reading.md) - converting with `get<T>()`, reading strings and numbers without copying, and writing a
|
||||||
|
view back with `dump()`
|
||||||
|
- [Writing generic code](generic_code.md) - functions and `from_json()` overloads that accept views
|
||||||
|
- [Editing a document](editing.md) - patching a document in place, keeping its member order and number spellings
|
||||||
|
- [Images](images.md) - saving a parsed document and loading it back without parsing
|
||||||
|
|
||||||
|
## 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) | [`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](images.md) and reload it without parsing | same, saving the document's current — possibly edited — state |
|
||||||
|
|
||||||
|
<p class="nj-table-caption" markdown>Each column is one way to work with JSON in this library, and each row compares one property of them.</p>
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [`basic_json_document`](../../api/basic_json_document/index.md) - the document that owns the parsed index (parse, access, images, edits)
|
||||||
|
- [`basic_json_view`](../../api/basic_json_view/index.md) - the non-owning handle to a value in a document
|
||||||
|
- [`json_document`](../../api/json_document.md) - and [`ordered_json_document`](../../api/ordered_json_document.md) - the read-only documents
|
||||||
|
- [`json_editable_document`](../../api/json_editable_document.md) - and [`ordered_json_editable_document`](../../api/ordered_json_editable_document.md) - the documents that can be edited in place
|
||||||
|
- [Architecture](../../home/architecture.md#node-index-of-json-views) - the layout of the node index
|
||||||
|
- [SAX interface](../parsing/sax_interface.md) - event-based parsing without building a tree
|
||||||
|
- [Performance](../performance.md) - benchmarks and tuning of parsing and serialization
|
||||||
|
- [Large, Streamed, and Partial Input](../parsing/large_and_partial_input.md) - reading only the part of a text you need
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# Ownership and lifetime
|
||||||
|
|
||||||
|
A document either **borrows** the text it was parsed from, or **owns** its own copy of it; call
|
||||||
|
[`owns_source()`](../../api/basic_json_document/owns_source.md) to find out which happened.
|
||||||
|
[`parse()`](../../api/basic_json_document/parse.md) decides this from the value category and type of its argument (an
|
||||||
|
lvalue `#!cpp std::string` is borrowed; an rvalue `#!cpp std::string` is moved in, owned without a copy; a stream is
|
||||||
|
read into an owned buffer; and so on — see [`parse`'s Notes](../../api/basic_json_document/parse.md#notes) for the
|
||||||
|
full table). [`parse_copy()`](../../api/basic_json_document/parse_copy.md) always owns a copy, regardless of the input.
|
||||||
|
|
||||||
|
!!! warning "A borrowed document depends on your buffer"
|
||||||
|
|
||||||
|
If a document borrows its text, that text **must outlive the document** (and every view taken from it) and must
|
||||||
|
stay **unmodified** for as long as the document exists, since floating-point values and unescaped strings are
|
||||||
|
read from it lazily. Reading or writing through a view after the underlying buffer is gone is undefined behavior,
|
||||||
|
exactly as it would be for a dangling `#!cpp std::string_view`.
|
||||||
|
|
||||||
|
## When a view is valid
|
||||||
|
|
||||||
|
A view is valid only while all of the following hold:
|
||||||
|
|
||||||
|
- the document is alive,
|
||||||
|
- the document has not been re-parsed since the view was taken (with
|
||||||
|
[`read()`](../../api/basic_json_document/read.md) or [`parse()`](../../api/basic_json_document/parse.md) into it),
|
||||||
|
- the document has not had [`shrink_to_fit()`](../../api/basic_json_document/shrink_to_fit.md) called on it since, and
|
||||||
|
- if the document borrows its source text, that text is still alive and unmodified.
|
||||||
|
|
||||||
|
Moving the `basic_json_document<>` itself is fine and does **not** invalidate its views: the index is a separate heap
|
||||||
|
allocation that keeps its address across the move. Take a fresh view from
|
||||||
|
[`root()`](../../api/basic_json_document/root.md) whenever any of the other conditions above was not met. Edits of an
|
||||||
|
[editable document](editing.md) keep views valid, too; see [What stays valid](editing.md#what-stays-valid-and-what-an-edit-costs).
|
||||||
|
|
||||||
|
<div class="nj-fig-wrap" markdown>
|
||||||
|
--8<-- "images/json_view_lifetime.svg"
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="nj-caption" markdown>Four timelines of a document and its views, each bar showing when an object is alive or valid and when it dangles. Destroying the borrowed text, re-parsing, or shrinking the document invalidates the views taken before; moving the document does not.</p>
|
||||||
|
|
||||||
|
## Why views can dangle
|
||||||
|
|
||||||
|
A view can dangle, exactly like a `#!cpp std::string_view`: nothing stops you from using it after its document is
|
||||||
|
gone. This is deliberate. A view is two pointers and trivially copyable; making it safe (a `#!cpp std::shared_ptr` in
|
||||||
|
the document and a `#!cpp std::weak_ptr` in each view) would make it larger, add reference counting to every copy,
|
||||||
|
and a `lock()` to every access — exactly the costs the view exists to avoid. What is caught is the obvious case:
|
||||||
|
because a view dies with its document, [`root()`](../../api/basic_json_document/root.md) is not callable on a
|
||||||
|
temporary document: `#!cpp auto v = json_document::parse(text).root();` does not compile. Give the document a name
|
||||||
|
first.
|
||||||
|
|
||||||
|
The same holds for what a view hands out without copying: a string from
|
||||||
|
[`get_string()`](../../api/basic_json_view/get_string.md), a token from
|
||||||
|
[`number_token()`](../../api/basic_json_view/number_token.md), and [`string_view` members](reading.md#string_view-members)
|
||||||
|
of decoded structs are valid only as long as the document and, if it borrows, its source text.
|
||||||
|
|
||||||
|
??? example "Example: borrowed and owned documents, and when views become invalid"
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
--8<-- "examples/json_view_ownership.cpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
--8<-- "examples/json_view_ownership.output"
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [Zero-copy JSON views](index.md) - overview
|
||||||
|
- [`basic_json_document`](../../api/basic_json_document/index.md) - the document that owns the parsed index
|
||||||
|
- [`owns_source()`](../../api/basic_json_document/owns_source.md) - whether a document borrows its source text
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Reading values
|
||||||
|
|
||||||
|
## Getting values out without copying
|
||||||
|
|
||||||
|
[`get<T>()`](../../api/basic_json_view/get.md) converts most `T` directly from the flat index, without ever building a
|
||||||
|
`basic_json` value for the conversion: `#!cpp bool`, arithmetic types, `#!cpp std::nullptr_t`,
|
||||||
|
`#!cpp std::string`/other `#!cpp std::basic_string`s (copied once), `basic_json_view` itself, the standard containers
|
||||||
|
and other types the library converts (`#!cpp std::vector`, `#!cpp std::map`, `#!cpp std::list`, `#!cpp std::array`,
|
||||||
|
`#!cpp std::pair`, `#!cpp std::tuple`, `#!cpp std::optional`, ...), and the types of the `NLOHMANN_DEFINE_TYPE_*` and
|
||||||
|
`NLOHMANN_JSON_SERIALIZE_ENUM` macros. Types whose `from_json()` is written for `basic_json` only go through
|
||||||
|
[`materialize()`](../../api/basic_json_view/materialize.md)`.get<T>()` instead: the subtree is built into a real
|
||||||
|
`basic_json` value first, exactly as [`parse()`](../../api/basic_json/parse.md) would, and converted from there. See
|
||||||
|
[A `from_json()` that reads views](generic_code.md#a-from_json-that-reads-views) for how to write a `from_json()` that
|
||||||
|
reads views, and [Duplicate keys](differences.md#duplicate-keys) for the one case in which the two ways give different
|
||||||
|
results.
|
||||||
|
|
||||||
|
Two conversions never copy at all:
|
||||||
|
|
||||||
|
- [`get_string()`](../../api/basic_json_view/get_string.md) (equivalently, `#!cpp get<string_view_t>()`) returns a
|
||||||
|
string as a `string_view_t` pointing into the document's [`source()`](../../api/basic_json_document/source.md) text
|
||||||
|
— or, for a string that contains escape sequences, into the document's own buffer of decoded strings — instead of
|
||||||
|
allocating a new `#!cpp std::string`.
|
||||||
|
- [`number_token()`](../../api/basic_json_view/number_token.md) returns a number exactly as it was written in the
|
||||||
|
source, e.g. `#!cpp "1.50"`, `#!cpp "1E2"`, or an integer with more digits than any number type holds, instead of
|
||||||
|
rounding it into a `#!cpp double`/`#!cpp int64_t` the way `#!cpp get<T>()` (and
|
||||||
|
[`basic_json::parse()`](../../api/basic_json/parse.md)) would.
|
||||||
|
|
||||||
|
Both results are only valid as long as the view — and, for a string with no escapes, the borrowed source text — is.
|
||||||
|
|
||||||
|
### `string_view` members
|
||||||
|
|
||||||
|
A struct whose member is a `#!cpp std::string_view` (C++17) can be decoded from a view with the
|
||||||
|
`NLOHMANN_DEFINE_TYPE_*` macros, and the member is not copied either: for a string without escape sequences it points
|
||||||
|
into the document's source text, and for a string with escape sequences into the document's buffer of decoded
|
||||||
|
strings. The member is valid as long as the document is alive and, for a document that
|
||||||
|
[borrows its source text](lifetime.md) (as [`parse()`](../../api/basic_json_document/parse.md) does for an lvalue
|
||||||
|
`#!cpp std::string`, a `#!cpp std::string_view`, or a C string), as long as that text is.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct record
|
||||||
|
{
|
||||||
|
std::string_view name;
|
||||||
|
int count;
|
||||||
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE(record, name, count)
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string text = R"({"name": "widget", "count": 3})";
|
||||||
|
const json_document doc = json_document::parse(text); // borrows text
|
||||||
|
const record r = doc.root().get<record>(); // r.name points into text, valid while text and doc are
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning "Decoding into a `string_view` from a `json` value dangles at once"
|
||||||
|
|
||||||
|
The same struct also compiles with `#!cpp json::parse(text).get<record>()`, but there `name` points into the
|
||||||
|
temporary `json` value, which is destroyed at the end of the statement. Use a named
|
||||||
|
[`json_document`](../../api/json_document.md), as above, to decode such a struct.
|
||||||
|
|
||||||
|
## Writing a view back
|
||||||
|
|
||||||
|
[`dump()`](../../api/basic_json_view/dump.md) serializes a view directly from the flat index, without ever building a
|
||||||
|
`basic_json` value. An object's members are written in document order, not sorted by key, and *every* occurrence of a
|
||||||
|
repeated key is written, not only the last one — the same two ways
|
||||||
|
[iteration differs](differences.md#object-member-order) from a
|
||||||
|
[`materialize()`](../../api/basic_json_view/materialize.md)d value, see
|
||||||
|
[Duplicate keys](differences.md#duplicate-keys). `#!cpp materialize().dump()` gives a different result in both
|
||||||
|
respects for a `json_view`.
|
||||||
|
|
||||||
|
By default, numbers are written the way [`basic_json::dump()`](../../api/basic_json/dump.md) would.
|
||||||
|
[`number_format::source`](../../api/basic_json_view/number_format.md) instead copies every number exactly as it was
|
||||||
|
written in the source text — a price like `#!cpp 19.90`, a long order or account ID with more digits than any number
|
||||||
|
type holds, or a high-precision coordinate — something `basic_json` cannot do at all, since parsing already reduces
|
||||||
|
a number to its parsed `#!cpp double`/`#!cpp int64_t` value. Together with an [editable document](editing.md), this
|
||||||
|
lets you patch a file without reformatting the rest of it.
|
||||||
|
|
||||||
|
[`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.
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [Zero-copy JSON views](index.md) - overview
|
||||||
|
- [`get`](../../api/basic_json_view/get.md) - convert a view
|
||||||
|
- [`dump`](../../api/basic_json_view/dump.md) - serialize a view
|
||||||
|
- [Writing generic code](generic_code.md) - functions and `from_json()` overloads that accept views
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -88,7 +88,7 @@ a.erase(1); // [1,3,4] (erase by index)
|
|||||||
## Editing a document
|
## Editing a document
|
||||||
|
|
||||||
The operations above work on a `basic_json` value. A JSON text that was parsed into a
|
The operations above work on a `basic_json` value. A JSON text that was parsed into a
|
||||||
[document](json_view.md#editing-a-document) can also be changed in place, without building a `basic_json` tree for the
|
[document](json_view/editing.md) can also be changed in place, without building a `basic_json` tree for the
|
||||||
parts that are not touched: [`set`](../api/basic_json_document/set.md) replaces a value, adds or replaces an object
|
parts that are not touched: [`set`](../api/basic_json_document/set.md) replaces a value, adds or replaces an object
|
||||||
member, or replaces an array element (also through a JSON pointer),
|
member, or replaces an array element (also through a JSON pointer),
|
||||||
[`push_back`](../api/basic_json_document/push_back.md) appends to an array,
|
[`push_back`](../api/basic_json_document/push_back.md) appends to an array,
|
||||||
@@ -105,7 +105,7 @@ editable document types, such as [`json_editable_document`](../api/json_editable
|
|||||||
- [`merge_patch`](../api/basic_json/merge_patch.md) - apply an RFC 7396 merge patch
|
- [`merge_patch`](../api/basic_json/merge_patch.md) - apply an RFC 7396 merge patch
|
||||||
- [`erase`](../api/basic_json/erase.md) - / [`clear`](../api/basic_json/clear.md) - remove elements
|
- [`erase`](../api/basic_json/erase.md) - / [`clear`](../api/basic_json/clear.md) - remove elements
|
||||||
- [JSON Patch and Diff](json_patch.md) - and [JSON Merge Patch](merge_patch.md) - structured modifications
|
- [JSON Patch and Diff](json_patch.md) - and [JSON Merge Patch](merge_patch.md) - structured modifications
|
||||||
- [Editing a document](json_view.md#editing-a-document) - change a parsed document in place
|
- [Editing a document](json_view/editing.md) - change a parsed document in place
|
||||||
- [`insert`](../api/basic_json/insert.md) - insert elements at an iterator position or merge a range
|
- [`insert`](../api/basic_json/insert.md) - insert elements at an iterator position or merge a range
|
||||||
- [`operator+=`](../api/basic_json/operator+=.md) - shorthand for `push_back`
|
- [`operator+=`](../api/basic_json/operator+=.md) - shorthand for `push_back`
|
||||||
- [Element Access](element_access/index.md) - read elements
|
- [Element Access](element_access/index.md) - read elements
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ The library offers two parsing models, and a third, read-only alternative:
|
|||||||
reads the same JSON grammar but builds a flat, read-only index of the values instead of a `basic_json` tree. Strings and
|
reads the same JSON grammar but builds a flat, read-only index of the values instead of a `basic_json` tree. Strings and
|
||||||
floating-point numbers are read from the text on demand, and only the parts you
|
floating-point numbers are read from the text on demand, and only the parts you
|
||||||
[`materialize()`](../../api/basic_json_view/materialize.md) become `basic_json` values. This avoids most of the work when you
|
[`materialize()`](../../api/basic_json_view/materialize.md) become `basic_json` values. This avoids most of the work when you
|
||||||
need only a few values from a large or frequently parsed text; see [Zero-copy JSON views](../json_view.md). It is read-only, so use
|
need only a few values from a large or frequently parsed text; see [Zero-copy JSON views](../json_view/index.md). It is read-only, so use
|
||||||
`parse` when you need to modify the value.
|
`parse` when you need to modify the value.
|
||||||
|
|
||||||
You can influence a DOM parse without switching to the SAX interface by passing a
|
You can influence a DOM parse without switching to the SAX interface by passing a
|
||||||
@@ -134,7 +134,7 @@ json_document doc = json_document::parse(R"({"happy": true, "pi": 3.141})");
|
|||||||
std::cout << doc.root()["happy"].get<bool>() << '\n';
|
std::cout << doc.root()["happy"].get<bool>() << '\n';
|
||||||
```
|
```
|
||||||
|
|
||||||
See [Zero-copy JSON views](../json_view.md) for the concept and the trade-offs.
|
See [Zero-copy JSON views](../json_view/index.md) for the concept and the trade-offs.
|
||||||
|
|
||||||
## Exceptions
|
## Exceptions
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ options.
|
|||||||
- [Large, streamed, and partial input](large_and_partial_input.md) - chunked, concatenated, and very large input
|
- [Large, streamed, and partial input](large_and_partial_input.md) - chunked, concatenated, and very large input
|
||||||
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
|
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
|
||||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||||
- [Zero-copy JSON views](../json_view.md) - parse into a read-only document and read it through views
|
- [Zero-copy JSON views](../json_view/index.md) - parse into a read-only document and read it through views
|
||||||
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
||||||
- [parsing untrusted input](untrusted_input.md) - what to consider when parsing input from untrusted sources
|
- [parsing untrusted input](untrusted_input.md) - what to consider when parsing input from untrusted sources
|
||||||
- [`operator>>`](../../api/operator_gtgt.md) - deserialize from stream
|
- [`operator>>`](../../api/operator_gtgt.md) - deserialize from stream
|
||||||
|
|||||||
@@ -156,11 +156,11 @@ record the reason in the handler (here: `id`) if you need to tell the two apart.
|
|||||||
|
|
||||||
### Read parts of a large document with a view
|
### Read parts of a large document with a view
|
||||||
|
|
||||||
If the whole text is available and you only need to read some of it, a [`json_document`](../json_view.md) from
|
If the whole text is available and you only need to read some of it, a [`json_document`](../json_view/index.md) from
|
||||||
`<nlohmann/json_view.hpp>` (added in version 3.13.0) avoids the `basic_json` tree altogether: it keeps the text, builds a flat index of the values,
|
`<nlohmann/json_view.hpp>` (added in version 3.13.0) avoids the `basic_json` tree altogether: it keeps the text, builds a flat index of the values,
|
||||||
and gives read-only [views](../../api/basic_json_view/index.md) of the parts you look at. Only what you
|
and gives read-only [views](../../api/basic_json_view/index.md) of the parts you look at. Only what you
|
||||||
[`materialize()`](../../api/basic_json_view/materialize.md) becomes a `basic_json` value. See
|
[`materialize()`](../../api/basic_json_view/materialize.md) becomes a `basic_json` value. See
|
||||||
[Zero-copy JSON views](../json_view.md) for details and for the lifetime rules.
|
[Zero-copy JSON views](../json_view/index.md) for details and for the lifetime rules.
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ and gives read-only [views](../../api/basic_json_view/index.md) of the parts you
|
|||||||
- [JSON Lines](json_lines.md) - newline-delimited JSON
|
- [JSON Lines](json_lines.md) - newline-delimited JSON
|
||||||
- [Parser callbacks](parser_callbacks.md) - discard values while parsing
|
- [Parser callbacks](parser_callbacks.md) - discard values while parsing
|
||||||
- [SAX interface](sax_interface.md) - event-based parsing
|
- [SAX interface](sax_interface.md) - event-based parsing
|
||||||
- [Zero-copy JSON views](../json_view.md) - read parts of a large document without building a tree
|
- [Zero-copy JSON views](../json_view/index.md) - read parts of a large document without building a tree
|
||||||
- [Untrusted input](untrusted_input.md) - limits for input you do not control
|
- [Untrusted input](untrusted_input.md) - limits for input you do not control
|
||||||
- [`operator>>`](../../api/operator_gtgt.md) - deserialize from stream
|
- [`operator>>`](../../api/operator_gtgt.md) - deserialize from stream
|
||||||
- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input
|
- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input
|
||||||
|
|||||||
@@ -71,12 +71,12 @@ a DOM, three alternatives avoid building it:
|
|||||||
discard finished elements as soon as they are handled, so memory usage stays bounded by one element (plus the
|
discard finished elements as soon as they are handled, so memory usage stays bounded by one element (plus the
|
||||||
unparsed remainder of the input) instead of the whole document — see the [recipe for streaming a large homogeneous
|
unparsed remainder of the input) instead of the whole document — see the [recipe for streaming a large homogeneous
|
||||||
array](parsing/parser_callbacks.md#recipe-streaming-a-large-homogeneous-array).
|
array](parsing/parser_callbacks.md#recipe-streaming-a-large-homogeneous-array).
|
||||||
- Parse into a [`json_document`](json_view.md) (`#!cpp <nlohmann/json_view.hpp>`) instead of a `basic_json`. It keeps
|
- Parse into a [`json_document`](json_view/index.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 floating-point numbers are not copied, but
|
the input text and builds a flat index of 16 bytes per value; strings and floating-point 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
|
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
|
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
|
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).
|
[choosing between `json`, the SAX interface, and `json_view`](json_view/index.md#choosing-between-json-ordered_json-the-sax-interface-and-json_view).
|
||||||
|
|
||||||
If the data is naturally record-oriented, consider [JSON Lines](parsing/json_lines.md) instead of one large JSON
|
If the data is naturally record-oriented, consider [JSON Lines](parsing/json_lines.md) instead of one large JSON
|
||||||
document: reading and parsing it line by line with `#!cpp std::getline` means only one line's value is ever in memory
|
document: reading and parsing it line by line with `#!cpp std::getline` means only one line's value is ever in memory
|
||||||
@@ -180,11 +180,11 @@ an adapter, since their template argument order does not match what `basic_json`
|
|||||||
large value therefore doubles its memory; see [Avoiding copies](#avoiding-copies). `#!cpp auto x = j["key"];` is a
|
large value therefore doubles its memory; see [Avoiding copies](#avoiding-copies). `#!cpp auto x = j["key"];` is a
|
||||||
copy, `#!cpp auto& x = j["key"];` is not.
|
copy, `#!cpp auto& x = j["key"];` is not.
|
||||||
- **A moved-from value is `null`.** After `#!cpp json c = std::move(a);` the value `a` is `#!json null` and owns nothing.
|
- **A moved-from value is `null`.** After `#!cpp json c = std::move(a);` the value `a` is `#!json null` and owns nothing.
|
||||||
- **Reading does not require a tree.** If you only read the data, parse it into a [`json_document`](json_view.md)
|
- **Reading does not require a tree.** If you only read the data, parse it into a [`json_document`](json_view/index.md)
|
||||||
(`#!cpp <nlohmann/json_view.hpp>`, added in version 3.13.0) instead of a `basic_json`. It stores 16 bytes per value, does not copy floating-point
|
(`#!cpp <nlohmann/json_view.hpp>`, added in version 3.13.0) instead of a `basic_json`. It stores 16 bytes per value, does not copy floating-point
|
||||||
numbers or strings without escape sequences out of the text, and creates no `basic_json` value except for the parts
|
numbers or strings without escape sequences out of the text, and creates no `basic_json` value except for the parts
|
||||||
you [`materialize()`](../api/basic_json_view/materialize.md). A document that borrows the text needs the text to
|
you [`materialize()`](../api/basic_json_view/materialize.md). A document that borrows the text needs the text to
|
||||||
outlive it; see [Zero-copy JSON views](json_view.md).
|
outlive it; see [Zero-copy JSON views](json_view/index.md).
|
||||||
- **Containers keep their capacity.** `clear()` and `erase()` on an array remove the elements, but the underlying
|
- **Containers keep their capacity.** `clear()` and `erase()` on an array remove the elements, but the underlying
|
||||||
`#!cpp std::vector` keeps its allocated capacity, like `#!cpp std::vector::clear` does. To give the memory back,
|
`#!cpp std::vector` keeps its allocated capacity, like `#!cpp std::vector::clear` does. To give the memory back,
|
||||||
assign a new value (`#!cpp j = json::array();`) or destroy the value.
|
assign a new value (`#!cpp j = json::array();`) or destroy the value.
|
||||||
@@ -265,7 +265,7 @@ those headers are then never processed by the compiler at all.
|
|||||||
- [Architecture](../home/architecture.md) - how input adapters, the lexer, and the serializer fit together
|
- [Architecture](../home/architecture.md) - how input adapters, the lexer, and the serializer fit together
|
||||||
- [Parsing](parsing/index.md) - the available parsing functions and inputs
|
- [Parsing](parsing/index.md) - the available parsing functions and inputs
|
||||||
- [SAX interface](parsing/sax_interface.md) - parse without building a DOM
|
- [SAX interface](parsing/sax_interface.md) - parse without building a DOM
|
||||||
- [Zero-copy JSON views](json_view.md) - parse into a flat index of the text and read it without building a DOM
|
- [Zero-copy JSON views](json_view/index.md) - parse into a flat index of the text and read it without building a DOM
|
||||||
- [Binary formats](binary_formats/index.md) - compact alternatives to JSON text
|
- [Binary formats](binary_formats/index.md) - compact alternatives to JSON text
|
||||||
- [Object Order](object_order.md) - `json` vs. `ordered_json` and other `ObjectType` choices
|
- [Object Order](object_order.md) - `json` vs. `ordered_json` and other `ObjectType` choices
|
||||||
- [Template Parameter Requirements](types/template_parameters.md) - custom container and allocator types
|
- [Template Parameter Requirements](types/template_parameters.md) - custom container and allocator types
|
||||||
|
|||||||
@@ -406,6 +406,7 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
|
|||||||
| `#!cpp operator<<(std::ostream&, const json_pointer&)` | streamability to `#!cpp std::ostream` |
|
| `#!cpp operator<<(std::ostream&, const json_pointer&)` | streamability to `#!cpp std::ostream` |
|
||||||
| [`to_string`](../../api/basic_json/to_string.md) | conversion of `StringType` to `#!cpp std::string` (the function returns a `#!cpp std::string`) |
|
| [`to_string`](../../api/basic_json/to_string.md) | conversion of `StringType` to `#!cpp std::string` (the function returns a `#!cpp std::string`) |
|
||||||
| exception messages | `data()` and `size()`, or `begin()` and `end()` |
|
| exception messages | `data()` and `size()`, or `begin()` and `end()` |
|
||||||
|
| [`basic_json_view::dump`](../../api/basic_json_view/dump.md) | `reserve(n)` and `resize(n)` (one argument); `capacity()` is **not** required |
|
||||||
|
|
||||||
### Compatible types
|
### Compatible types
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ The public headers are in [`include/nlohmann`](https://github.com/nlohmann/json/
|
|||||||
- [`json_view.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/json_view.hpp) is a separate,
|
- [`json_view.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/json_view.hpp) is a separate,
|
||||||
optional header that defines [`basic_json_document`](../api/basic_json_document/index.md) and
|
optional header that defines [`basic_json_document`](../api/basic_json_document/index.md) and
|
||||||
[`basic_json_view`](../api/basic_json_view/index.md), a flat-index, read-only, non-owning way to look at a parsed
|
[`basic_json_view`](../api/basic_json_view/index.md), a flat-index, read-only, non-owning way to look at a parsed
|
||||||
JSON text; see [Zero-copy JSON views](../features/json_view.md). It builds on `json.hpp` internals (it requires the
|
JSON text; see [Zero-copy JSON views](../features/json_view/index.md). It builds on `json.hpp` internals (it requires the
|
||||||
same library version) and has its own `detail/view/` subdirectory.
|
same library version) and has its own `detail/view/` subdirectory.
|
||||||
|
|
||||||
Everything else lives in [`detail/`](https://github.com/nlohmann/json/tree/develop/include/nlohmann/detail) and namespace `nlohmann::detail`, which is not part of the public API. Paths
|
Everything else lives in [`detail/`](https://github.com/nlohmann/json/tree/develop/include/nlohmann/detail) and namespace `nlohmann::detail`, which is not part of the public API. Paths
|
||||||
@@ -175,7 +175,7 @@ maintains the invariant that the pointer matching `m_type` is never null; `asser
|
|||||||
|
|
||||||
## Node index of JSON views
|
## Node index of JSON views
|
||||||
|
|
||||||
A [`basic_json_document`](../api/basic_json_document/index.md) (see [Zero-copy JSON views](../features/json_view.md))
|
A [`basic_json_document`](../api/basic_json_document/index.md) (see [Zero-copy JSON views](../features/json_view/index.md))
|
||||||
does not build a tree of values. Its parser
|
does not build a tree of values. Its parser
|
||||||
([`detail/view/builder.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/builder.hpp))
|
([`detail/view/builder.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/builder.hpp))
|
||||||
writes a flat array of 16-byte nodes, one per value and one per object key, in document order. A
|
writes a flat array of 16-byte nodes, one per value and one per object key, in document order. A
|
||||||
@@ -466,7 +466,7 @@ release. Besides the components above, it contains:
|
|||||||
- [`adl_serializer`](../api/adl_serializer/index.md) - customization point for type conversions
|
- [`adl_serializer`](../api/adl_serializer/index.md) - customization point for type conversions
|
||||||
- [`json_sax`](../api/json_sax/index.md) - interface of the SAX parser events
|
- [`json_sax`](../api/json_sax/index.md) - interface of the SAX parser events
|
||||||
- [SAX Interface](../features/parsing/sax_interface.md) - how to consume parser events without building a DOM
|
- [SAX Interface](../features/parsing/sax_interface.md) - how to consume parser events without building a DOM
|
||||||
- [Zero-copy JSON views](../features/json_view.md) - the flat node index that backs views and documents
|
- [Zero-copy JSON views](../features/json_view/index.md) - the flat node index that backs views and documents
|
||||||
- [Template Parameter Requirements](../features/types/template_parameters.md) - the template parameters of basic_json
|
- [Template Parameter Requirements](../features/types/template_parameters.md) - the template parameters of basic_json
|
||||||
- [Serialization](../features/serialization.md) - the output side of the library
|
- [Serialization](../features/serialization.md) - the output side of the library
|
||||||
- [`nlohmann` Namespace](../features/namespace.md) - how the library namespace is structured
|
- [`nlohmann` Namespace](../features/namespace.md) - how the library namespace is structured
|
||||||
@@ -10,6 +10,13 @@ at its root, a [Natvis](https://learn.microsoft.com/en-us/visualstudio/debugger/
|
|||||||
file that gives `json`/`ordered_json` values a friendly, key/value debugger view instead of showing raw internal
|
file that gives `json`/`ordered_json` values a friendly, key/value debugger view instead of showing raw internal
|
||||||
fields, when debugging with the MSVC debug engine (`cppvsdbg`) in Visual Studio or VS Code.
|
fields, when debugging with the MSVC debug engine (`cppvsdbg`) in Visual Studio or VS Code.
|
||||||
|
|
||||||
|
The file also has visualizers for `json_document` and `json_view` (and their `ordered_json`/editable variants). They
|
||||||
|
show the value of the document or view, and its elements as an expandable list. Because a document is only a flat index
|
||||||
|
into the source text, the strings of the direct elements are shown, but the elements of nested arrays and objects are
|
||||||
|
shown as index nodes (their kind, size, and numbers; strings only by length). Arrays and objects that have been edited
|
||||||
|
in a `json_editable_document` are not expanded. The visualizers read the internal layout of the MSVC STL (`std::array`,
|
||||||
|
`std::unique_ptr`).
|
||||||
|
|
||||||
The checked-in visualizers cover namespace variants with zero, one, or two ABI tags. Configurations with three or more
|
The checked-in visualizers cover namespace variants with zero, one, or two ABI tags. Configurations with three or more
|
||||||
ABI tags are not covered and may show raw internal fields instead. To generate a Natvis file for such a configuration:
|
ABI tags are not covered and may show raw internal fields instead. To generate a Natvis file for such a configuration:
|
||||||
|
|
||||||
@@ -29,7 +36,7 @@ ABI tags are not covered and may show raw internal fields instead. To generate a
|
|||||||
|
|
||||||
4. Use the generated file instead of `nlohmann_json.natvis`.
|
4. Use the generated file instead of `nlohmann_json.natvis`.
|
||||||
|
|
||||||
`--abi-tags` can be repeated for several configurations, and `--all` generates every combination (about 60 MB for 12
|
`--abi-tags` can be repeated for several configurations, and `--all` generates every combination (about 60 MB for 11
|
||||||
ABI tags, doubling with each further tag). See
|
ABI tags, doubling with each further tag). See
|
||||||
[`tools/generate_natvis`](https://github.com/nlohmann/json/tree/develop/tools/generate_natvis) for all options.
|
[`tools/generate_natvis`](https://github.com/nlohmann/json/tree/develop/tools/generate_natvis) for all options.
|
||||||
|
|
||||||
|
|||||||
@@ -438,7 +438,7 @@ A UBJSON or BJData high-precision number could not be parsed.
|
|||||||
### json.exception.parse_error.116
|
### json.exception.parse_error.116
|
||||||
|
|
||||||
[`basic_json_document::load()`](../api/basic_json_document/load.md) rejected an
|
[`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)
|
[image](../features/json_view/images.md): 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
|
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).
|
they are, but fail the requested [`image_check`](../api/basic_json_document/load.md#image_check).
|
||||||
|
|
||||||
@@ -450,7 +450,7 @@ they are, but fail the requested [`image_check`](../api/basic_json_document/load
|
|||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
This exception was added in version 3.13.0, together with [images](../features/json_view.md#images).
|
This exception was added in version 3.13.0, together with [images](../features/json_view/images.md).
|
||||||
|
|
||||||
## Iterator errors
|
## Iterator errors
|
||||||
|
|
||||||
@@ -889,12 +889,12 @@ from JSON text.
|
|||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
This exception was added in version 3.13.0, together with editable [`json_document`s](../features/json_view.md).
|
This exception was added in version 3.13.0, together with editable [`json_document`s](../features/json_view/index.md).
|
||||||
|
|
||||||
### json.exception.type_error.320
|
### json.exception.type_error.320
|
||||||
|
|
||||||
[`basic_json_document::save()`](../api/basic_json_document/save.md) cannot write an
|
[`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.
|
[image](../features/json_view/images.md) of a [discarded](../api/basic_json_document/is_discarded.md) document.
|
||||||
|
|
||||||
!!! failure "Example messages"
|
!!! failure "Example messages"
|
||||||
|
|
||||||
@@ -904,7 +904,7 @@ from JSON text.
|
|||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
This exception was added in version 3.13.0, together with [images](../features/json_view.md#images).
|
This exception was added in version 3.13.0, together with [images](../features/json_view/images.md).
|
||||||
|
|
||||||
### json.exception.type_error.321
|
### json.exception.type_error.321
|
||||||
|
|
||||||
@@ -1192,7 +1192,7 @@ so they do not support an input of 4294967280 bytes (4 GiB minus 16 bytes) or mo
|
|||||||
storage: [`set`](../api/basic_json_document/set.md) and [`push_back`](../api/basic_json_document/push_back.md) throw
|
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
|
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
|
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
|
[image](../features/json_view/images.md): [`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.
|
count, the text, or the decoded strings it would write would individually reach 4 GiB.
|
||||||
|
|
||||||
!!! failure "Example messages"
|
!!! failure "Example messages"
|
||||||
@@ -1209,7 +1209,7 @@ count, the text, or the decoded strings it would write would individually reach
|
|||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
This exception was added in version 3.13.0, together with [`<nlohmann/json_view.hpp>`](../features/json_view.md).
|
This exception was added in version 3.13.0, together with [`<nlohmann/json_view.hpp>`](../features/json_view/index.md).
|
||||||
[`basic_json::parse()`](../api/basic_json/parse.md) has no such limit.
|
[`basic_json::parse()`](../api/basic_json/parse.md) has no such limit.
|
||||||
|
|
||||||
## Further exceptions
|
## Further exceptions
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ Read newline-delimited input line by line ([JSON Lines](../features/parsing/json
|
|||||||
|
|
||||||
Handle and discard the records of a large array with a [parser callback](../features/parsing/parser_callbacks.md), stop
|
Handle and discard the records of a large array with a [parser callback](../features/parsing/parser_callbacks.md), stop
|
||||||
early with the [SAX interface](../features/parsing/sax_interface.md), or read parts of the text through a
|
early with the [SAX interface](../features/parsing/sax_interface.md), or read parts of the text through a
|
||||||
[`json_document`](../features/json_view.md) (added in version 3.13.0). See
|
[`json_document`](../features/json_view/index.md) (added in version 3.13.0). See
|
||||||
[Input too large for memory](../features/parsing/large_and_partial_input.md#input-too-large-for-memory).
|
[Input too large for memory](../features/parsing/large_and_partial_input.md#input-too-large-for-memory).
|
||||||
|
|
||||||
### Position of a parse error
|
### Position of a parse error
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ Clang).
|
|||||||
anything, or link a library. Put the file in a directory named `nlohmann` that is on your include path (for
|
anything, or link a library. Put the file in a directory named `nlohmann` that is on your include path (for
|
||||||
example `third_party/nlohmann/json.hpp` with `-I third_party`), so that `#include <nlohmann/json.hpp>` works.
|
example `third_party/nlohmann/json.hpp` with `-I third_party`), so that `#include <nlohmann/json.hpp>` works.
|
||||||
Download the companion headers listed below only if you use them: `json_view.hpp` for the
|
Download the companion headers listed below only if you use them: `json_view.hpp` for the
|
||||||
[zero-copy views](../features/json_view.md), `json_fwd.hpp` for forward declarations, and `json_literals.hpp` if
|
[zero-copy views](../features/json_view/index.md), `json_fwd.hpp` for forward declarations, and `json_literals.hpp` if
|
||||||
you define `JSON_NO_AUTOMATIC_UDLS`. Keep them in the same `nlohmann` directory.
|
you define `JSON_NO_AUTOMATIC_UDLS`. Keep them in the same `nlohmann` directory.
|
||||||
|
|
||||||
Do not copy only parts of the repository's `include/` directory: the headers in it include each other and must be
|
Do not copy only parts of the repository's `include/` directory: the headers in it include each other and must be
|
||||||
@@ -75,7 +75,7 @@ for the user-defined string literals if you define
|
|||||||
For the read-only, non-owning [`basic_json_document`](../api/basic_json_document/index.md)/[`basic_json_view`](../api/basic_json_view/index.md)
|
For the read-only, non-owning [`basic_json_document`](../api/basic_json_document/index.md)/[`basic_json_view`](../api/basic_json_view/index.md)
|
||||||
types, additionally include
|
types, additionally include
|
||||||
[`single_include/nlohmann/json_view.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json_view.hpp);
|
[`single_include/nlohmann/json_view.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json_view.hpp);
|
||||||
see [Zero-copy JSON views](../features/json_view.md).
|
see [Zero-copy JSON views](../features/json_view/index.md).
|
||||||
|
|
||||||
## Notes for builds and distribution
|
## Notes for builds and distribution
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* figures: jsonview -- stepper for the edit frames on features/json_view.md.
|
/* figures: jsonview -- stepper for the edit frames on features/json_view/index.md.
|
||||||
Without JS the frames (.nj-jsonview-step) show one below the other; with JS one frame at a time, with
|
Without JS the frames (.nj-jsonview-step) show one below the other; with JS one frame at a time, with
|
||||||
previous/next and one button per step. Never advances on its own. */
|
previous/next and one button per step. Never advances on its own. */
|
||||||
(function () {
|
(function () {
|
||||||
|
|||||||
@@ -157,7 +157,14 @@ nav:
|
|||||||
- features/types/index.md
|
- features/types/index.md
|
||||||
- features/types/number_handling.md
|
- features/types/number_handling.md
|
||||||
- features/types/template_parameters.md
|
- features/types/template_parameters.md
|
||||||
- features/json_view.md
|
- Zero-copy views:
|
||||||
|
- features/json_view/index.md
|
||||||
|
- features/json_view/lifetime.md
|
||||||
|
- features/json_view/differences.md
|
||||||
|
- features/json_view/reading.md
|
||||||
|
- features/json_view/generic_code.md
|
||||||
|
- features/json_view/editing.md
|
||||||
|
- features/json_view/images.md
|
||||||
- Integration:
|
- Integration:
|
||||||
- integration/index.md
|
- integration/index.md
|
||||||
- integration/migration_guide.md
|
- integration/migration_guide.md
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
wheel==0.48.0
|
wheel==0.48.0
|
||||||
|
|
||||||
mkdocs==1.6.1 # documentation framework
|
mkdocs==1.6.1 # documentation framework
|
||||||
mkdocs-git-revision-date-localized-plugin==1.6.0 # plugin "git-revision-date-localized"
|
mkdocs-git-revision-date-localized-plugin==1.6.2 # plugin "git-revision-date-localized"
|
||||||
mkdocs-material==9.7.7 # theme for mkdocs
|
mkdocs-material==9.8.0 # theme for mkdocs
|
||||||
mkdocs-material-extensions==1.3.1 # extensions
|
mkdocs-material-extensions==1.3.1 # extensions
|
||||||
mkdocs-minify-plugin==0.8.0 # plugin "minify"
|
mkdocs-minify-plugin==0.8.0 # plugin "minify"
|
||||||
mkdocs-redirects==1.2.3 # plugin "redirects"
|
mkdocs-redirects==1.2.3 # plugin "redirects"
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ const typename BasicJsonType::binary_t& binary_value(const BasicJsonType& j)
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename View, enable_if_t<is_json_view<View>::value, int> = 0>
|
template<typename View, enable_if_t<is_json_view<View>::value, int> = 0>
|
||||||
typename View::basic_json_type::binary_t binary_value(const View& /*unused*/)
|
typename View::basic_json_type::binary_t binary_value(const View& /*unused*/) // LCOV_EXCL_LINE
|
||||||
{
|
{
|
||||||
return {}; // LCOV_EXCL_LINE
|
return {}; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,18 +198,19 @@ class exception : public std::exception
|
|||||||
// all other parent types (primitives) contribute no token
|
// all other parent types (primitives) contribute no token
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokens.empty())
|
// the root value has no path, but still has a byte range
|
||||||
|
std::string prefix;
|
||||||
|
if (!tokens.empty())
|
||||||
{
|
{
|
||||||
return "";
|
const auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
||||||
}
|
|
||||||
|
|
||||||
auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
|
||||||
[](const std::string & a, const std::string & b)
|
[](const std::string & a, const std::string & b)
|
||||||
{
|
{
|
||||||
return concat(a, '/', detail::escape(b));
|
return concat(a, '/', detail::escape(b));
|
||||||
});
|
});
|
||||||
|
prefix = concat('(', str, ") ");
|
||||||
|
}
|
||||||
|
|
||||||
return concat('(', str, ") ", get_byte_positions(leaf_element));
|
return concat(prefix, get_byte_positions(leaf_element));
|
||||||
#else
|
#else
|
||||||
return get_byte_positions(leaf_element);
|
return get_byte_positions(leaf_element);
|
||||||
#endif
|
#endif
|
||||||
@@ -222,7 +223,8 @@ class exception : public std::exception
|
|||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static std::string get_byte_positions(const BasicJsonType* leaf_element)
|
static std::string get_byte_positions(const BasicJsonType* leaf_element)
|
||||||
{
|
{
|
||||||
if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos))
|
// a value-initialized iterator has no value (invalid_iterator.212)
|
||||||
|
if (leaf_element != nullptr && leaf_element->start_pos() != std::string::npos && leaf_element->end_pos() != std::string::npos)
|
||||||
{
|
{
|
||||||
return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
|
return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
|
||||||
}
|
}
|
||||||
@@ -431,6 +433,28 @@ std::nullptr_t exception_context(const View& /*unused*/) noexcept
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace view
|
||||||
|
{
|
||||||
|
// the path and bytes of the value of a view (defined in <nlohmann/detail/view/errors.hpp>)
|
||||||
|
template<typename View>
|
||||||
|
std::string diagnostics_of(const View& v);
|
||||||
|
} // namespace view
|
||||||
|
|
||||||
|
// what goes in front of the message of an exception about j: nothing for a
|
||||||
|
// basic_json value (exception_context() passes the value itself), the context
|
||||||
|
// basic_json would add (JSON_DIAGNOSTICS, JSON_DIAGNOSTIC_POSITIONS) for a view
|
||||||
|
template<typename BasicJsonType, enable_if_t<is_basic_json<BasicJsonType>::value, int> = 0>
|
||||||
|
const char* exception_prefix(const BasicJsonType& /*unused*/) noexcept
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename View, enable_if_t<is_json_view<View>::value, int> = 0>
|
||||||
|
std::string exception_prefix(const View& v)
|
||||||
|
{
|
||||||
|
return view::diagnostics_of(v);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief throws because @a j does not have the type a conversion expects
|
@brief throws because @a j does not have the type a conversion expects
|
||||||
@param[in] expected the expected type(s), e.g. "array" or "binary or array"
|
@param[in] expected the expected type(s), e.g. "array" or "binary or array"
|
||||||
@@ -442,7 +466,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const cha
|
|||||||
{
|
{
|
||||||
static_cast<void>(expected); // unused when JSON_NOEXCEPTION is defined
|
static_cast<void>(expected); // unused when JSON_NOEXCEPTION is defined
|
||||||
static_cast<void>(j);
|
static_cast<void>(j);
|
||||||
JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), exception_context(j)));
|
JSON_THROW(type_error::create(exception_id::type_mismatch, concat(exception_prefix(j), "type must be ", expected, ", but is ", j.type_name()), exception_context(j)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <bit> //byteswap
|
#include <bit> //byteswap
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <nlohmann/detail/bit_ops.hpp>
|
||||||
#include <nlohmann/detail/exceptions.hpp>
|
#include <nlohmann/detail/exceptions.hpp>
|
||||||
#include <nlohmann/detail/input/input_adapters.hpp>
|
#include <nlohmann/detail/input/input_adapters.hpp>
|
||||||
#include <nlohmann/detail/input/json_sax.hpp>
|
#include <nlohmann/detail/input/json_sax.hpp>
|
||||||
@@ -465,15 +466,19 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
|
|
||||||
const std::size_t element_type_parse_position = chars_read;
|
const std::size_t element_type_parse_position = chars_read;
|
||||||
|
// an array's elements are named "0", "1", ... in the wire format,
|
||||||
|
// and those names are not passed on: they are skipped, and only
|
||||||
|
// checked if error_handler_t::strict must reject ill-formed UTF-8
|
||||||
|
const bool is_object = container_stack.back().is_object;
|
||||||
key.clear();
|
key.clear();
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key)))
|
if (JSON_HEDLEY_UNLIKELY(!(is_object || error_handler != error_handler_t::strict
|
||||||
|
? get_bson_cstr(key, is_object)
|
||||||
|
: check_bson_cstr(key, std::integral_constant<bool, bulk_scan> {}))))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// an array's elements are named "0", "1", ... in the wire format,
|
if (is_object && !sax->key(key))
|
||||||
// and those names are not passed on
|
|
||||||
if (container_stack.back().is_object && !sax->key(key))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -489,14 +494,18 @@ class binary_reader
|
|||||||
@brief Parses a C-style string from the BSON input.
|
@brief Parses a C-style string from the BSON input.
|
||||||
@param[in,out] result A reference to the string variable where the read
|
@param[in,out] result A reference to the string variable where the read
|
||||||
string is to be stored.
|
string is to be stored.
|
||||||
|
@param[in] store whether the characters are needed; if not (the names of
|
||||||
|
an array's elements), they are skipped and @a result may
|
||||||
|
be left unchanged, and they are not checked against
|
||||||
|
@ref error_handler (@ref check_bson_cstr checks them)
|
||||||
@return `true` if the \\x00-byte indicating the end of the string was
|
@return `true` if the \\x00-byte indicating the end of the string was
|
||||||
encountered before the EOF; false` indicates an unexpected EOF.
|
encountered before the EOF; false` indicates an unexpected EOF.
|
||||||
*/
|
*/
|
||||||
bool get_bson_cstr(string_t& result)
|
bool get_bson_cstr(string_t& result, const bool store = true)
|
||||||
{
|
{
|
||||||
if (get_bson_cstr_bulk(result, std::integral_constant<bool, bulk_scan> {}))
|
if (get_bson_cstr_bulk(result, store, std::integral_constant<bool, bulk_scan> {}))
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, "key");
|
return !store || check_string_utf8(result, "key");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto out = std::back_inserter(result);
|
auto out = std::back_inserter(result);
|
||||||
@@ -509,7 +518,7 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
if (current == 0x00)
|
if (current == 0x00)
|
||||||
{
|
{
|
||||||
return check_string_utf8(result, "key");
|
return !store || check_string_utf8(result, "key");
|
||||||
}
|
}
|
||||||
*out++ = static_cast<typename string_t::value_type>(current);
|
*out++ = static_cast<typename string_t::value_type>(current);
|
||||||
}
|
}
|
||||||
@@ -519,10 +528,11 @@ class binary_reader
|
|||||||
@brief read a C-style string from contiguous input in one step
|
@brief read a C-style string from contiguous input in one step
|
||||||
|
|
||||||
@param[in,out] result the string to append to
|
@param[in,out] result the string to append to
|
||||||
|
@param[in] store whether to append the characters to @a result
|
||||||
@return whether the string was read; if the input has no \\x00-byte, nothing
|
@return whether the string was read; if the input has no \\x00-byte, nothing
|
||||||
is read, and @ref get_bson_cstr reports the end of the input
|
is read, and @ref get_bson_cstr reports the end of the input
|
||||||
*/
|
*/
|
||||||
bool get_bson_cstr_bulk(string_t& result, std::true_type /*bulk*/)
|
bool get_bson_cstr_bulk(string_t& result, const bool store, std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
const std::size_t remaining = ia.bulk_remaining();
|
const std::size_t remaining = ia.bulk_remaining();
|
||||||
if (remaining == 0)
|
if (remaining == 0)
|
||||||
@@ -530,18 +540,15 @@ class binary_reader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||||
// a plain loop rather than std::memchr: most keys are short (array
|
const std::size_t length = find_zero_byte(data, remaining);
|
||||||
// indices are keys, too), and the call would cost more than it saves
|
|
||||||
std::size_t length = 0;
|
|
||||||
while (length < remaining && data[length] != 0x00)
|
|
||||||
{
|
|
||||||
++length;
|
|
||||||
}
|
|
||||||
if (length == remaining)
|
if (length == remaining)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (store)
|
||||||
|
{
|
||||||
result.append(reinterpret_cast<const typename string_t::value_type*>(data), length);
|
result.append(reinterpret_cast<const typename string_t::value_type*>(data), length);
|
||||||
|
}
|
||||||
// consume the string and its \x00-byte, as the byte-wise path does
|
// consume the string and its \x00-byte, as the byte-wise path does
|
||||||
ia.bulk_skip(length + 1);
|
ia.bulk_skip(length + 1);
|
||||||
chars_read += length + 1;
|
chars_read += length + 1;
|
||||||
@@ -549,8 +556,44 @@ class binary_reader
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief skip a C-style string that error_handler_t::strict must check
|
||||||
|
|
||||||
|
The names of an array's elements are not passed on. On contiguous input,
|
||||||
|
a well-formed name is checked in place and skipped without being copied.
|
||||||
|
Otherwise (no \\x00-byte in the input that can be scanned, or ill-formed
|
||||||
|
UTF-8), it is read by @ref get_bson_cstr, which reports the error.
|
||||||
|
|
||||||
|
@param[in,out] result an empty string, which may receive the name
|
||||||
|
@return whether the name was read and is well-formed
|
||||||
|
*/
|
||||||
|
bool check_bson_cstr(string_t& result, std::true_type /*bulk*/)
|
||||||
|
{
|
||||||
|
const std::size_t remaining = ia.bulk_remaining();
|
||||||
|
if (remaining != 0)
|
||||||
|
{
|
||||||
|
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||||
|
const std::size_t length = find_zero_byte(data, remaining);
|
||||||
|
if (length != remaining && JSON_HEDLEY_LIKELY(valid_utf8_string(data, length)))
|
||||||
|
{
|
||||||
|
// consume the string and its \x00-byte, as get_bson_cstr does
|
||||||
|
ia.bulk_skip(length + 1);
|
||||||
|
chars_read += length + 1;
|
||||||
|
current = 0x00;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return get_bson_cstr(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// input that is not contiguous: the name is read and checked byte by byte
|
||||||
|
bool check_bson_cstr(string_t& result, std::false_type /*bulk*/)
|
||||||
|
{
|
||||||
|
return get_bson_cstr(result);
|
||||||
|
}
|
||||||
|
|
||||||
/// input that is not contiguous: C-style strings are read byte by byte
|
/// input that is not contiguous: C-style strings are read byte by byte
|
||||||
bool get_bson_cstr_bulk(string_t& /*result*/, std::false_type /*bulk*/) const noexcept
|
bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -741,11 +784,13 @@ class binary_reader
|
|||||||
bool get_cbor_negative_integer()
|
bool get_cbor_negative_integer()
|
||||||
{
|
{
|
||||||
NumberType number{};
|
NumberType number{};
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_number(number)))
|
return get_number(number) && emit_cbor_negative_integer(number);
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// pass the CBOR negative integer -1 - @a number to the SAX parser
|
||||||
|
template<typename NumberType>
|
||||||
|
bool emit_cbor_negative_integer(const NumberType number)
|
||||||
|
{
|
||||||
// the value is -1 - number, which fits into number_integer_t
|
// the value is -1 - number, which fits into number_integer_t
|
||||||
// whenever number does; the outer cast undoes the integral promotion
|
// whenever number does; the outer cast undoes the integral promotion
|
||||||
// for number_integer_t types narrower than int
|
// for number_integer_t types narrower than int
|
||||||
@@ -970,7 +1015,7 @@ class binary_reader
|
|||||||
case 0x95:
|
case 0x95:
|
||||||
case 0x96:
|
case 0x96:
|
||||||
case 0x97:
|
case 0x97:
|
||||||
return enter_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
|
return enter_cbor_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
|
||||||
|
|
||||||
case 0x98: // array (one-byte uint8_t for n follows)
|
case 0x98: // array (one-byte uint8_t for n follows)
|
||||||
case 0x99: // array (two-byte uint16_t for n follow)
|
case 0x99: // array (two-byte uint16_t for n follow)
|
||||||
@@ -979,7 +1024,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
std::uint64_t len{};
|
std::uint64_t len{};
|
||||||
std::size_t size{};
|
std::size_t size{};
|
||||||
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_array(size);
|
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_cbor_array(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x9F: // array (indefinite length)
|
case 0x9F: // array (indefinite length)
|
||||||
@@ -1133,16 +1178,10 @@ class binary_reader
|
|||||||
return get_half_float(false);
|
return get_half_float(false);
|
||||||
|
|
||||||
case 0xFA: // Single-Precision Float (four-byte IEEE 754)
|
case 0xFA: // Single-Precision Float (four-byte IEEE 754)
|
||||||
{
|
return get_cbor_float<float>();
|
||||||
float number{};
|
|
||||||
return get_number(number) && emit_float(number);
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0xFB: // Double-Precision Float (eight-byte IEEE 754)
|
case 0xFB: // Double-Precision Float (eight-byte IEEE 754)
|
||||||
{
|
return get_cbor_float<double>();
|
||||||
double number{};
|
|
||||||
return get_number(number) && emit_float(number);
|
|
||||||
}
|
|
||||||
|
|
||||||
default: // anything else (0xFF is handled inside the other types)
|
default: // anything else (0xFF is handled inside the other types)
|
||||||
{
|
{
|
||||||
@@ -1151,6 +1190,14 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// read a single- or double-precision float
|
||||||
|
template<typename NumberType>
|
||||||
|
bool get_cbor_float()
|
||||||
|
{
|
||||||
|
NumberType number{};
|
||||||
|
return get_number(number) && emit_float(number);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reports a nested indefinite-length CBOR string or byte array
|
@brief reports a nested indefinite-length CBOR string or byte array
|
||||||
@param[in] type_name name of the rejected string type
|
@param[in] type_name name of the rejected string type
|
||||||
@@ -1676,6 +1723,159 @@ class binary_reader
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read a big-endian number of type @a NumberType at @a data
|
||||||
|
*/
|
||||||
|
template<typename NumberType>
|
||||||
|
NumberType load_big_endian(const unsigned char* data) const noexcept
|
||||||
|
{
|
||||||
|
NumberType result{};
|
||||||
|
std::memcpy(&result, data, sizeof(NumberType));
|
||||||
|
if (is_little_endian)
|
||||||
|
{
|
||||||
|
byte_swap(result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read the integers, floats, booleans, and nulls that follow in a
|
||||||
|
definite-length array, without the per-element bookkeeping of
|
||||||
|
@ref parse_cbor_internal
|
||||||
|
|
||||||
|
Stops at the end of the array, at any other kind of value, and when fewer
|
||||||
|
than 9 bytes (the longest of these values) are left in the input, leaving
|
||||||
|
those to @ref parse_cbor_value, which reads them the same way.
|
||||||
|
|
||||||
|
Out of line, so that @ref parse_cbor_value stays small enough to be inlined.
|
||||||
|
|
||||||
|
The array is the one on top of @ref container_stack; its count of
|
||||||
|
elements not yet read is updated.
|
||||||
|
|
||||||
|
@return whether the SAX parser accepted the values
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
bool parse_cbor_scalar_run(std::true_type /*bulk*/)
|
||||||
|
{
|
||||||
|
// nothing pushes onto the stack while the run reads scalars
|
||||||
|
std::size_t& remaining = container_stack.back().remaining;
|
||||||
|
while (remaining != 0 && ia.bulk_remaining() >= 9)
|
||||||
|
{
|
||||||
|
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||||
|
const unsigned char head = data[0];
|
||||||
|
std::size_t size = 0;
|
||||||
|
bool accepted = false;
|
||||||
|
switch (head)
|
||||||
|
{
|
||||||
|
case 0x18:
|
||||||
|
size = 2;
|
||||||
|
accepted = emit_unsigned(data[1]);
|
||||||
|
break;
|
||||||
|
case 0x19:
|
||||||
|
size = 3;
|
||||||
|
accepted = emit_unsigned(load_big_endian<std::uint16_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0x1A:
|
||||||
|
size = 5;
|
||||||
|
accepted = emit_unsigned(load_big_endian<std::uint32_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0x1B:
|
||||||
|
size = 9;
|
||||||
|
accepted = emit_unsigned(load_big_endian<std::uint64_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0x38:
|
||||||
|
size = 2;
|
||||||
|
accepted = emit_cbor_negative_integer(data[1]);
|
||||||
|
break;
|
||||||
|
case 0x39:
|
||||||
|
size = 3;
|
||||||
|
accepted = emit_cbor_negative_integer(load_big_endian<std::uint16_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0x3A:
|
||||||
|
size = 5;
|
||||||
|
accepted = emit_cbor_negative_integer(load_big_endian<std::uint32_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0x3B:
|
||||||
|
size = 9;
|
||||||
|
accepted = emit_cbor_negative_integer(load_big_endian<std::uint64_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0xF4:
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->boolean(false);
|
||||||
|
break;
|
||||||
|
case 0xF5:
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->boolean(true);
|
||||||
|
break;
|
||||||
|
case 0xF6:
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->null();
|
||||||
|
break;
|
||||||
|
case 0xFA:
|
||||||
|
size = 5;
|
||||||
|
accepted = emit_float(load_big_endian<float>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0xFB:
|
||||||
|
size = 9;
|
||||||
|
accepted = emit_float(load_big_endian<double>(data + 1));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (head <= 0x17)
|
||||||
|
{
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->number_unsigned(static_cast<number_unsigned_t>(head));
|
||||||
|
}
|
||||||
|
else if (head >= 0x20 && head <= 0x37)
|
||||||
|
{
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->number_integer(static_cast<std::int8_t>(0x20 - 1 - head));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// any other value is read by parse_cbor_value
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// as if read by get() and get_number(): current is the head byte
|
||||||
|
ia.bulk_skip(size);
|
||||||
|
chars_read += size;
|
||||||
|
current = head;
|
||||||
|
--remaining;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!accepted))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief open a definite-length array and read the scalars it begins with
|
||||||
|
(see @ref parse_cbor_scalar_run)
|
||||||
|
|
||||||
|
Only the start of an array is tried, which covers arrays of numbers
|
||||||
|
(e.g. coordinates) but costs arrays of other values only one call, not one
|
||||||
|
per element. An array of one element is not tried at all: that element is
|
||||||
|
mostly an object or an array (`[[...]]`, the single ring of a GeoJSON
|
||||||
|
polygon), for which the call would find no scalar, while a single scalar,
|
||||||
|
which the run would read faster, is rare. The benchmarks
|
||||||
|
`FromBinaryShape/nested` and `FromBinaryShape/singles` show both sides.
|
||||||
|
|
||||||
|
@param[in] len the number of elements
|
||||||
|
@return whether the SAX parser accepted the array and the values read
|
||||||
|
*/
|
||||||
|
bool enter_cbor_array(const std::size_t len)
|
||||||
|
{
|
||||||
|
return enter_array(len) && (len <= 1 || parse_cbor_scalar_run(std::integral_constant<bool, bulk_scan> {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// input that is not contiguous: every value is read by parse_cbor_value
|
||||||
|
bool parse_cbor_scalar_run(std::false_type /*bulk*/) const noexcept
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief read a CBOR value and everything nested inside it
|
@brief read a CBOR value and everything nested inside it
|
||||||
|
|
||||||
@@ -1705,6 +1905,7 @@ class binary_reader
|
|||||||
// pop_back() below, which destroys the container_stack element
|
// pop_back() below, which destroys the container_stack element
|
||||||
// it would otherwise alias
|
// it would otherwise alias
|
||||||
const container_frame top = container_stack.back();
|
const container_frame top = container_stack.back();
|
||||||
|
const bool is_object = top.is_object;
|
||||||
bool at_end = false;
|
bool at_end = false;
|
||||||
|
|
||||||
if (top.remaining != npos)
|
if (top.remaining != npos)
|
||||||
@@ -1716,7 +1917,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
// claim the element about to be read
|
// claim the element about to be read
|
||||||
--container_stack.back().remaining;
|
--container_stack.back().remaining;
|
||||||
if (top.is_object)
|
if (is_object)
|
||||||
{
|
{
|
||||||
get();
|
get();
|
||||||
}
|
}
|
||||||
@@ -1729,7 +1930,7 @@ class binary_reader
|
|||||||
// Testing for it consumes a byte, which is the first byte
|
// Testing for it consumes a byte, which is the first byte
|
||||||
// of the next element when it is not one.
|
// of the next element when it is not one.
|
||||||
at_end = (get() == 0xFF);
|
at_end = (get() == 0xFF);
|
||||||
fetch = top.is_object;
|
fetch = is_object;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (at_end)
|
if (at_end)
|
||||||
@@ -1746,7 +1947,7 @@ class binary_reader
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (top.is_object)
|
if (is_object)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
|
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
|
||||||
{
|
{
|
||||||
@@ -1971,7 +2172,7 @@ class binary_reader
|
|||||||
case 0x9D:
|
case 0x9D:
|
||||||
case 0x9E:
|
case 0x9E:
|
||||||
case 0x9F:
|
case 0x9F:
|
||||||
return enter_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x0Fu));
|
return enter_msgpack_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x0Fu));
|
||||||
|
|
||||||
// fixstr
|
// fixstr
|
||||||
case 0xA0:
|
case 0xA0:
|
||||||
@@ -2102,13 +2303,13 @@ class binary_reader
|
|||||||
case 0xDC: // array 16
|
case 0xDC: // array 16
|
||||||
{
|
{
|
||||||
std::uint16_t len{};
|
std::uint16_t len{};
|
||||||
return get_number(len) && enter_array(static_cast<std::size_t>(len));
|
return get_number(len) && enter_msgpack_array(static_cast<std::size_t>(len));
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0xDD: // array 32
|
case 0xDD: // array 32
|
||||||
{
|
{
|
||||||
std::uint32_t len{};
|
std::uint32_t len{};
|
||||||
return get_number(len) && enter_array(conditional_static_cast<std::size_t>(len));
|
return get_number(len) && enter_msgpack_array(conditional_static_cast<std::size_t>(len));
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0xDE: // map 16
|
case 0xDE: // map 16
|
||||||
@@ -2450,6 +2651,141 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read the integers, floats, booleans, and nils that follow in a
|
||||||
|
MessagePack array, without the per-element bookkeeping of
|
||||||
|
@ref parse_msgpack_internal
|
||||||
|
|
||||||
|
Stops at the end of the array, at any other kind of value, and when fewer
|
||||||
|
than 9 bytes (the longest of these values) are left in the input, leaving
|
||||||
|
those to @ref parse_msgpack_value, which reads them the same way.
|
||||||
|
|
||||||
|
Out of line, so that @ref parse_msgpack_value stays small enough to be inlined.
|
||||||
|
|
||||||
|
The array is the one on top of @ref container_stack; its count of
|
||||||
|
elements not yet read is updated.
|
||||||
|
|
||||||
|
@return whether the SAX parser accepted the values
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
bool parse_msgpack_scalar_run(std::true_type /*bulk*/)
|
||||||
|
{
|
||||||
|
// nothing pushes onto the stack while the run reads scalars
|
||||||
|
std::size_t& remaining = container_stack.back().remaining;
|
||||||
|
while (remaining != 0 && ia.bulk_remaining() >= 9)
|
||||||
|
{
|
||||||
|
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||||
|
const unsigned char head = data[0];
|
||||||
|
std::size_t size = 0;
|
||||||
|
bool accepted = false;
|
||||||
|
switch (head)
|
||||||
|
{
|
||||||
|
case 0xC0: // nil
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->null();
|
||||||
|
break;
|
||||||
|
case 0xC2: // false
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->boolean(false);
|
||||||
|
break;
|
||||||
|
case 0xC3: // true
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->boolean(true);
|
||||||
|
break;
|
||||||
|
case 0xCA: // float 32
|
||||||
|
size = 5;
|
||||||
|
accepted = emit_float(load_big_endian<float>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0xCB: // float 64
|
||||||
|
size = 9;
|
||||||
|
accepted = emit_float(load_big_endian<double>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0xCC: // uint 8
|
||||||
|
size = 2;
|
||||||
|
accepted = emit_unsigned(data[1]);
|
||||||
|
break;
|
||||||
|
case 0xCD: // uint 16
|
||||||
|
size = 3;
|
||||||
|
accepted = emit_unsigned(load_big_endian<std::uint16_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0xCE: // uint 32
|
||||||
|
size = 5;
|
||||||
|
accepted = emit_unsigned(load_big_endian<std::uint32_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0xCF: // uint 64
|
||||||
|
size = 9;
|
||||||
|
accepted = emit_unsigned(load_big_endian<std::uint64_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0xD0: // int 8
|
||||||
|
size = 2;
|
||||||
|
accepted = emit_signed(static_cast<std::int8_t>(data[1]));
|
||||||
|
break;
|
||||||
|
case 0xD1: // int 16
|
||||||
|
size = 3;
|
||||||
|
accepted = emit_signed(load_big_endian<std::int16_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0xD2: // int 32
|
||||||
|
size = 5;
|
||||||
|
accepted = emit_signed(load_big_endian<std::int32_t>(data + 1));
|
||||||
|
break;
|
||||||
|
case 0xD3: // int 64
|
||||||
|
size = 9;
|
||||||
|
accepted = emit_signed(load_big_endian<std::int64_t>(data + 1));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (head <= 0x7F)
|
||||||
|
{
|
||||||
|
// positive fixint
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->number_unsigned(static_cast<number_unsigned_t>(head));
|
||||||
|
}
|
||||||
|
else if (head >= 0xE0)
|
||||||
|
{
|
||||||
|
// negative fixint
|
||||||
|
size = 1;
|
||||||
|
accepted = sax->number_integer(static_cast<std::int8_t>(head));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// any other value is read by parse_msgpack_value
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// as if read by get() and get_number(): current is the last byte
|
||||||
|
ia.bulk_skip(size);
|
||||||
|
chars_read += size;
|
||||||
|
current = data[size - 1];
|
||||||
|
--remaining;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!accepted))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief open a MessagePack array and read the scalars it begins with (see
|
||||||
|
@ref parse_msgpack_scalar_run)
|
||||||
|
|
||||||
|
An array of one element is not tried, for the reason given at
|
||||||
|
@ref enter_cbor_array.
|
||||||
|
|
||||||
|
@param[in] len the number of elements
|
||||||
|
@return whether the SAX parser accepted the array and the values read
|
||||||
|
*/
|
||||||
|
bool enter_msgpack_array(const std::size_t len)
|
||||||
|
{
|
||||||
|
return enter_array(len) && (len <= 1 || parse_msgpack_scalar_run(std::integral_constant<bool, bulk_scan> {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// input that is not contiguous: every value is read by parse_msgpack_value
|
||||||
|
bool parse_msgpack_scalar_run(std::false_type /*bulk*/) const noexcept
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief read a MessagePack value and everything nested inside it
|
@brief read a MessagePack value and everything nested inside it
|
||||||
|
|
||||||
@@ -3255,6 +3591,107 @@ class binary_reader
|
|||||||
return unexpected_byte("invalid byte", "value");
|
return unexpected_byte("invalid byte", "value");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read the elements of an optimized array of numbers (type marker
|
||||||
|
@a type) that are in the input, in one loop, without the
|
||||||
|
per-element bookkeeping of @ref parse_ubjson_internal
|
||||||
|
|
||||||
|
Values are emitted as @ref get_ubjson_value emits them. Types other than
|
||||||
|
the fixed-size integers and floats are left to @ref get_ubjson_value, as
|
||||||
|
are the elements the input ends before.
|
||||||
|
|
||||||
|
@param[in,out] remaining the number of elements not yet read
|
||||||
|
@param[in] type the type marker of the elements
|
||||||
|
@return whether the SAX parser accepted the values
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
bool parse_ubjson_typed_run(std::size_t& remaining, const char_int_type type, std::true_type /*bulk*/)
|
||||||
|
{
|
||||||
|
const bool bjdata = input_format == input_format_t::bjdata;
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case 'U':
|
||||||
|
return parse_ubjson_typed_run<std::uint8_t>(remaining);
|
||||||
|
case 'i':
|
||||||
|
return parse_ubjson_typed_run<std::int8_t>(remaining);
|
||||||
|
case 'I':
|
||||||
|
return parse_ubjson_typed_run<std::int16_t>(remaining);
|
||||||
|
case 'l':
|
||||||
|
return parse_ubjson_typed_run<std::int32_t>(remaining);
|
||||||
|
case 'L':
|
||||||
|
return parse_ubjson_typed_run<std::int64_t>(remaining);
|
||||||
|
case 'u':
|
||||||
|
return !bjdata || parse_ubjson_typed_run<std::uint16_t>(remaining);
|
||||||
|
case 'm':
|
||||||
|
return !bjdata || parse_ubjson_typed_run<std::uint32_t>(remaining);
|
||||||
|
case 'M':
|
||||||
|
return !bjdata || parse_ubjson_typed_run<std::uint64_t>(remaining);
|
||||||
|
case 'd':
|
||||||
|
return parse_ubjson_typed_run<float>(remaining);
|
||||||
|
case 'D':
|
||||||
|
return parse_ubjson_typed_run<double>(remaining);
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// input that is not contiguous: every element is read by get_ubjson_value
|
||||||
|
bool parse_ubjson_typed_run(std::size_t& /*remaining*/, const char_int_type /*type*/, std::false_type /*bulk*/) const noexcept
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename NumberType>
|
||||||
|
bool parse_ubjson_typed_run(std::size_t& remaining)
|
||||||
|
{
|
||||||
|
constexpr std::size_t size = sizeof(NumberType);
|
||||||
|
const std::size_t count = (std::min)(remaining, ia.bulk_remaining() / size);
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||||
|
// UBJSON stores numbers big-endian, BJData little-endian
|
||||||
|
const bool swap = is_little_endian != (input_format == input_format_t::bjdata);
|
||||||
|
std::size_t read = 0;
|
||||||
|
bool accepted = true;
|
||||||
|
while (read < count && accepted)
|
||||||
|
{
|
||||||
|
NumberType number{};
|
||||||
|
std::memcpy(&number, data + (read * size), size);
|
||||||
|
if (swap)
|
||||||
|
{
|
||||||
|
byte_swap(number);
|
||||||
|
}
|
||||||
|
++read;
|
||||||
|
accepted = emit_ubjson_number(number);
|
||||||
|
}
|
||||||
|
// as if read by get_number(): current is the last byte read
|
||||||
|
ia.bulk_skip(read * size);
|
||||||
|
chars_read += read * size;
|
||||||
|
current = data[(read * size) - 1];
|
||||||
|
remaining -= read;
|
||||||
|
return accepted;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename NumberType, enable_if_t<std::is_floating_point<NumberType>::value, int> = 0>
|
||||||
|
bool emit_ubjson_number(const NumberType number)
|
||||||
|
{
|
||||||
|
return emit_float(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
template < typename NumberType, enable_if_t < std::is_integral<NumberType>::value&& std::is_signed<NumberType>::value, int > = 0 >
|
||||||
|
bool emit_ubjson_number(const NumberType number)
|
||||||
|
{
|
||||||
|
return emit_signed(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
template < typename NumberType, enable_if_t < std::is_integral<NumberType>::value&& !std::is_signed<NumberType>::value, int > = 0 >
|
||||||
|
bool emit_ubjson_number(const NumberType number)
|
||||||
|
{
|
||||||
|
return emit_unsigned(number);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@return whether array creation completed
|
@return whether array creation completed
|
||||||
*/
|
*/
|
||||||
@@ -3290,7 +3727,15 @@ class binary_reader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::size_t i = 0; i < size_and_type.first; ++i)
|
// the numbers that are in the input, in one loop; the rest (if
|
||||||
|
// the input ends early) one by one below
|
||||||
|
std::size_t remaining = size_and_type.first;
|
||||||
|
if (!nd_is_char && JSON_HEDLEY_UNLIKELY(!parse_ubjson_typed_run(remaining, size_and_type.second, std::integral_constant<bool, bulk_scan> {})))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = size_and_type.first - remaining; i < size_and_type.first; ++i)
|
||||||
{
|
{
|
||||||
if (nd_is_char)
|
if (nd_is_char)
|
||||||
{
|
{
|
||||||
@@ -3350,7 +3795,8 @@ class binary_reader
|
|||||||
container_stack.back().remaining = 0;
|
container_stack.back().remaining = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return size_and_type.second == 0
|
||||||
|
|| parse_ubjson_typed_run(container_stack.back().remaining, size_and_type.second, std::integral_constant<bool, bulk_scan> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
return enter_array(detail::unknown_size());
|
return enter_array(detail::unknown_size());
|
||||||
@@ -3691,13 +4137,13 @@ class binary_reader
|
|||||||
return sax->null();
|
return sax->null();
|
||||||
|
|
||||||
case 0xFB:
|
case 0xFB:
|
||||||
return sax->number_float(static_cast<number_float_t>(-1.0), "");
|
return sax->number_float(static_cast<number_float_t>(-1.0), no_source);
|
||||||
|
|
||||||
case 0xFC:
|
case 0xFC:
|
||||||
return sax->number_float(static_cast<number_float_t>(0.0), "");
|
return sax->number_float(static_cast<number_float_t>(0.0), no_source);
|
||||||
|
|
||||||
case 0xFD:
|
case 0xFD:
|
||||||
return sax->number_float(static_cast<number_float_t>(1.0), "");
|
return sax->number_float(static_cast<number_float_t>(1.0), no_source);
|
||||||
|
|
||||||
case 0xFF: // empty string
|
case 0xFF: // empty string
|
||||||
{
|
{
|
||||||
@@ -4020,7 +4466,12 @@ class binary_reader
|
|||||||
`char_traits<char_type>::eof()` in that case.
|
`char_traits<char_type>::eof()` in that case.
|
||||||
|
|
||||||
@return character read from the input
|
@return character read from the input
|
||||||
|
|
||||||
|
@note always inlined: GCC otherwise stops inlining the stream adapter's
|
||||||
|
get_character() into parse_cbor_internal once that function grows
|
||||||
|
(+0.45 % instructions for CBOR from a std::istringstream with GCC 13)
|
||||||
*/
|
*/
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
char_int_type get()
|
char_int_type get()
|
||||||
{
|
{
|
||||||
++chars_read;
|
++chars_read;
|
||||||
@@ -4066,6 +4517,27 @@ class binary_reader
|
|||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// reverse the byte order of a double with a single byteswap(): GCC does
|
||||||
|
/// not merge the byte-wise swap of the generic version into one
|
||||||
|
static void byte_swap(double& number) noexcept
|
||||||
|
{
|
||||||
|
static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
|
||||||
|
std::uint64_t bits{};
|
||||||
|
std::memcpy(&bits, &number, sizeof(bits));
|
||||||
|
bits = byteswap(bits);
|
||||||
|
std::memcpy(&number, &bits, sizeof(bits));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// reverse the byte order of a float with a single byteswap()
|
||||||
|
static void byte_swap(float& number) noexcept
|
||||||
|
{
|
||||||
|
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
|
||||||
|
std::uint32_t bits{};
|
||||||
|
std::memcpy(&bits, &number, sizeof(bits));
|
||||||
|
bits = byteswap(bits);
|
||||||
|
std::memcpy(&number, &bits, sizeof(bits));
|
||||||
|
}
|
||||||
|
|
||||||
template<class NumberType>
|
template<class NumberType>
|
||||||
static void byte_swap(NumberType& number)
|
static void byte_swap(NumberType& number)
|
||||||
{
|
{
|
||||||
@@ -4201,7 +4673,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
return out_of_range_error(exception_id::number_overflow, "number overflow", "value");
|
return out_of_range_error(exception_id::number_overflow, "number overflow", "value");
|
||||||
}
|
}
|
||||||
return sax->number_float(result, "");
|
return sax->number_float(result, no_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -4261,7 +4733,7 @@ class binary_reader
|
|||||||
}();
|
}();
|
||||||
return sax->number_float((half & 0x8000u) != 0
|
return sax->number_float((half & 0x8000u) != 0
|
||||||
? static_cast<number_float_t>(-val)
|
? static_cast<number_float_t>(-val)
|
||||||
: static_cast<number_float_t>(val), "");
|
: static_cast<number_float_t>(val), no_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -4293,6 +4765,19 @@ class binary_reader
|
|||||||
return get_bytes(len, "string", result);
|
return get_bytes(len, "string", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// whether @a s is valid UTF-8: ASCII eight bytes at a time, the rest
|
||||||
|
/// sequence by sequence (as the binary writers check strings)
|
||||||
|
static bool is_valid_utf8_string(const string_t& s, std::true_type /*is_byte*/) noexcept
|
||||||
|
{
|
||||||
|
return valid_utf8_string(reinterpret_cast<const unsigned char*>(s.data()), s.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// whether @a s, of a character type wider than a byte, is valid UTF-8
|
||||||
|
static bool is_valid_utf8_string(const string_t& s, std::false_type /*is_byte*/) noexcept
|
||||||
|
{
|
||||||
|
return is_valid_utf8(s);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief validate a decoded text string (value or object key) against @ref error_handler
|
@brief validate a decoded text string (value or object key) against @ref error_handler
|
||||||
|
|
||||||
@@ -4312,7 +4797,16 @@ class binary_reader
|
|||||||
*/
|
*/
|
||||||
bool check_string_utf8(string_t& result, const char* context)
|
bool check_string_utf8(string_t& result, const char* context)
|
||||||
{
|
{
|
||||||
if (error_handler == error_handler_t::keep || is_valid_utf8(result))
|
return error_handler == error_handler_t::keep || check_string_utf8_handler(result, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @ref check_string_utf8 for an error handler other than @ref
|
||||||
|
/// error_handler_t::keep; out of line, so that reading strings without a
|
||||||
|
/// check does not pay for the code of the check
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
bool check_string_utf8_handler(string_t& result, const char* context)
|
||||||
|
{
|
||||||
|
if (is_valid_utf8_string(result, std::integral_constant<bool, sizeof(typename string_t::value_type) == 1>()))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -4586,6 +5080,10 @@ class binary_reader
|
|||||||
/// the SAX parser
|
/// the SAX parser
|
||||||
json_sax_t* sax = nullptr;
|
json_sax_t* sax = nullptr;
|
||||||
|
|
||||||
|
/// the (empty) source text of every number_float event: binary formats
|
||||||
|
/// have none, and a string_t built for each float would cost more
|
||||||
|
const string_t no_source{};
|
||||||
|
|
||||||
/// the containers that have been opened and not closed yet; see @ref container_frame
|
/// the containers that have been opened and not closed yet; see @ref container_frame
|
||||||
std::vector<container_frame> container_stack{};
|
std::vector<container_frame> container_stack{};
|
||||||
|
|
||||||
|
|||||||
@@ -545,12 +545,9 @@ class input_stream_adapter
|
|||||||
std::char_traits<char>::int_type get_character_slow()
|
std::char_traits<char>::int_type get_character_slow()
|
||||||
{
|
{
|
||||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||||
// sbumpc() refills the get area if it is exhausted
|
// the get area is exhausted (get_character() handles the other case),
|
||||||
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
|
// so sbumpc() refills it
|
||||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
|
||||||
{
|
|
||||||
save_token_segment();
|
save_token_segment();
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto res = sb->sbumpc();
|
auto res = sb->sbumpc();
|
||||||
@@ -563,17 +560,13 @@ class input_stream_adapter
|
|||||||
is->clear(is->rdstate() | std::ios::eofbit);
|
is->clear(is->rdstate() | std::ios::eofbit);
|
||||||
}
|
}
|
||||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||||
else if (JSON_HEDLEY_UNLIKELY(refill))
|
else
|
||||||
{
|
{
|
||||||
// the consumed character is at gptr() - 1, unless the streambuf
|
// the consumed character is at gptr() - 1, unless the streambuf
|
||||||
// has no get area and returned it from uflow()
|
// has no get area and returned it from uflow()
|
||||||
const char* const next = streambuf_access::get_ptr(sb);
|
const char* const next = streambuf_access::get_ptr(sb);
|
||||||
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
|
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
current_in_get_area = true;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -999,7 +999,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
|||||||
ordered_data.members[f.ordered.dest_member].second = std::move(value);
|
ordered_data.members[f.ordered.dest_member].second = std::move(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void place_member(const frame& /*f*/, BasicJsonType&& /*value*/, std::false_type /*indexed*/) noexcept {}
|
static void place_member(const frame& /*f*/, BasicJsonType&& /*value*/, std::false_type /*indexed*/) noexcept {} // LCOV_EXCL_LINE
|
||||||
|
|
||||||
/// store the members of a new object directly in it
|
/// store the members of a new object directly in it
|
||||||
bool start_stored_object(std::size_t len)
|
bool start_stored_object(std::size_t len)
|
||||||
@@ -1303,7 +1303,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
|||||||
{
|
{
|
||||||
// a new member is null and holds nothing: construct the value
|
// a new member is null and holds nothing: construct the value
|
||||||
// over it instead of destroying and swapping
|
// over it instead of destroying and swapping
|
||||||
::new (static_cast<void*>(&member)) BasicJsonType(std::forward<Value>(v));
|
construct_member(member, std::forward<Value>(v), std::is_nothrow_constructible<BasicJsonType, Value> {});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1312,6 +1312,26 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// constructing the value cannot throw (for example a number or a boolean): construct it in place
|
||||||
|
template<typename Value>
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
static void construct_member(BasicJsonType& member, Value&& v, std::true_type /*nothrow*/)
|
||||||
|
{
|
||||||
|
::new (static_cast<void*>(&member)) BasicJsonType(std::forward<Value>(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// constructing the value can throw (for example a container or a string), and a
|
||||||
|
/// constructor that was interrupted must not have touched the member (#5985):
|
||||||
|
/// create the value first, then move it over the member (the move constructor is
|
||||||
|
/// noexcept)
|
||||||
|
template<typename Value>
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
static void construct_member(BasicJsonType& member, Value&& v, std::false_type /*may throw*/)
|
||||||
|
{
|
||||||
|
BasicJsonType value(std::forward<Value>(v));
|
||||||
|
::new (static_cast<void*>(&member)) BasicJsonType(std::move(value));
|
||||||
|
}
|
||||||
|
|
||||||
/// a custom base class may care about the lifetime of the value
|
/// a custom base class may care about the lifetime of the value
|
||||||
template<typename Value>
|
template<typename Value>
|
||||||
static void set_member(BasicJsonType& member, Value&& v, std::false_type /*custom base*/)
|
static void set_member(BasicJsonType& member, Value&& v, std::false_type /*custom base*/)
|
||||||
@@ -1737,7 +1757,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
|
|||||||
key_stack.push_back(val);
|
key_stack.push_back(val);
|
||||||
|
|
||||||
// add discarded value at the given key and store the reference for later
|
// add discarded value at the given key and store the reference for later
|
||||||
if (keep && ref_stack.back())
|
if (keep)
|
||||||
{
|
{
|
||||||
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
||||||
// a single lookup: insert the placeholder, or find the existing member
|
// a single lookup: insert the placeholder, or find the existing member
|
||||||
@@ -1794,7 +1814,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
|
|||||||
|
|
||||||
const string_t object_key = pop_container();
|
const string_t object_key = pop_container();
|
||||||
|
|
||||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
|
if (!ref_stack.empty() && ref_stack.back())
|
||||||
{
|
{
|
||||||
// remove discarded value
|
// remove discarded value
|
||||||
remove_discarded_value(*ref_stack.back(), object_key);
|
remove_discarded_value(*ref_stack.back(), object_key);
|
||||||
@@ -1960,8 +1980,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
|
|||||||
*/
|
*/
|
||||||
string_t current_key() const
|
string_t current_key() const
|
||||||
{
|
{
|
||||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object()
|
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object())
|
||||||
&& !key_stack.empty())
|
|
||||||
{
|
{
|
||||||
return key_stack.back();
|
return key_stack.back();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,8 +209,13 @@ constexpr bool input_adapter_bulk_window(std::false_type /*detected*/)
|
|||||||
@brief lexical analysis
|
@brief lexical analysis
|
||||||
|
|
||||||
This class organizes the lexical analysis during JSON deserialization.
|
This class organizes the lexical analysis during JSON deserialization.
|
||||||
|
|
||||||
|
DiscardValues: the caller only needs the token types and never looks at the values of number and string tokens (only
|
||||||
|
accept(), which parses through json_sax_acceptor). A template parameter rather than a member, so that the lexer of
|
||||||
|
parse() is compiled without the paths of accept(): with both string scans in one scan(), parse() was up to 2 % slower
|
||||||
|
on Apple M1.
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType, bool DiscardValues = false>
|
||||||
class lexer : public lexer_base<BasicJsonType>
|
class lexer : public lexer_base<BasicJsonType>
|
||||||
{
|
{
|
||||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||||
@@ -268,10 +273,9 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
public:
|
public:
|
||||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||||
|
|
||||||
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept
|
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept
|
||||||
: ia(std::move(adapter))
|
: ia(std::move(adapter))
|
||||||
, ignore_comments(ignore_comments_)
|
, ignore_comments(ignore_comments_)
|
||||||
, discard_number_values(discard_number_values_)
|
|
||||||
{
|
{
|
||||||
// the token string of an error before the first token is everything
|
// the token string of an error before the first token is everything
|
||||||
// read so far
|
// read so far
|
||||||
@@ -296,14 +300,16 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
/// calls would (including the position and token bookkeeping of a
|
/// calls would (including the position and token bookkeeping of a
|
||||||
/// streaming adapter, see bulk_consume) and leaves @a current holding the last of
|
/// streaming adapter, see bulk_consume) and leaves @a current holding the last of
|
||||||
/// the 4 digits, just as the last such get() would; the codepoint is
|
/// the 4 digits, just as the last such get() would; the codepoint is
|
||||||
/// written to @a out. Makes no state change and returns false for a
|
/// written to @a out. Makes no state change and returns false for fewer
|
||||||
/// pending unget, fewer than 4 remaining bytes, or any of the 4 bytes not
|
/// than 4 remaining bytes or any of the 4 bytes not being a hex digit, so
|
||||||
/// being a hex digit, so the caller falls back unchanged to the
|
/// the caller falls back unchanged to the per-character loop, which then
|
||||||
/// per-character loop, which then reports the same diagnostic (stopping
|
/// reports the same diagnostic (stopping at the first invalid digit) as
|
||||||
/// at the first invalid digit) as before this optimization.
|
/// before this optimization.
|
||||||
bool get_codepoint_bulk(std::true_type /*bulk*/, int& out)
|
bool get_codepoint_bulk(std::true_type /*bulk*/, int& out)
|
||||||
{
|
{
|
||||||
if (next_unget || ia.bulk_remaining() < 4)
|
// no unget is pending inside a string (see get_after_string_run())
|
||||||
|
JSON_ASSERT(!next_unget);
|
||||||
|
if (ia.bulk_remaining() < 4)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -388,6 +394,8 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
checks if it is inside the range. If a violation was detected, set up an
|
checks if it is inside the range. If a violation was detected, set up an
|
||||||
error message and return false. Otherwise, return true.
|
error message and return false. Otherwise, return true.
|
||||||
|
|
||||||
|
@param[in] keep whether the bytes are added to token_buffer (false for
|
||||||
|
accept(), see scan())
|
||||||
@param[in] ranges list of integers; interpreted as list of pairs of
|
@param[in] ranges list of integers; interpreted as list of pairs of
|
||||||
inclusive lower and upper bound, respectively
|
inclusive lower and upper bound, respectively
|
||||||
|
|
||||||
@@ -396,17 +404,18 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
|
|
||||||
@return true if and only if no range violation was detected
|
@return true if and only if no range violation was detected
|
||||||
*/
|
*/
|
||||||
bool next_byte_in_range(std::initializer_list<char_int_type> ranges)
|
template<bool Keep>
|
||||||
|
bool next_byte_in_range(std::integral_constant<bool, Keep> keep, std::initializer_list<char_int_type> ranges)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6);
|
JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6);
|
||||||
add(current);
|
add_string_char(keep, current);
|
||||||
|
|
||||||
for (auto range = ranges.begin(); range != ranges.end(); ++range)
|
for (auto range = ranges.begin(); range != ranges.end(); ++range)
|
||||||
{
|
{
|
||||||
get();
|
get();
|
||||||
if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions)
|
if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions)
|
||||||
{
|
{
|
||||||
add(current);
|
add_string_char(keep, current);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -419,20 +428,21 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// contiguous input: bulk-append the run of ordinary characters and complete
|
/// contiguous input: bulk-append the run of ordinary characters and complete
|
||||||
/// well-formed UTF-8 sequences starting at the current read position, then
|
/// well-formed UTF-8 sequences starting at the current read position (only
|
||||||
|
/// skip it if @a keep is false), then
|
||||||
/// read the first byte that needs individual handling (the closing quote, an
|
/// read the first byte that needs individual handling (the closing quote, an
|
||||||
/// escape, a control character, or an ill-formed UTF-8 byte) like get().
|
/// escape, a control character, or an ill-formed UTF-8 byte) like get().
|
||||||
/// That byte is taken from the buffer that was just scanned. Through get(),
|
/// That byte is taken from the buffer that was just scanned. Through get(),
|
||||||
/// Apple Clang merged this read with the other get() calls of the scan
|
/// Apple Clang merged this read with the other get() calls of the scan
|
||||||
/// function, which load the read position again right after it was stored
|
/// function, which load the read position again right after it was stored
|
||||||
/// (3% of json::accept() on jeopardy.json on Apple M1).
|
/// (3% of json::accept() on jeopardy.json on Apple M1).
|
||||||
char_int_type get_after_string_run(std::true_type /*bulk*/)
|
template<bool Keep>
|
||||||
|
char_int_type get_after_string_run(std::true_type /*bulk*/, std::integral_constant<bool, Keep> keep)
|
||||||
{
|
{
|
||||||
// a pending unget must be consumed through the normal path first
|
// scan_string() is entered from scan_after_whitespace(), whose current
|
||||||
if (next_unget)
|
// was read by skip_whitespace() (its first get() clears a pending
|
||||||
{
|
// unget), and nothing in scan_string() calls unget()
|
||||||
return get();
|
JSON_ASSERT(!next_unget);
|
||||||
}
|
|
||||||
const std::size_t remaining = ia.bulk_remaining();
|
const std::size_t remaining = ia.bulk_remaining();
|
||||||
if (remaining == 0)
|
if (remaining == 0)
|
||||||
{
|
{
|
||||||
@@ -444,7 +454,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
const std::size_t pos = lexer_string_bulk_run(data, remaining);
|
const std::size_t pos = lexer_string_bulk_run(data, remaining);
|
||||||
if (pos != 0)
|
if (pos != 0)
|
||||||
{
|
{
|
||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
append_string_run(keep, data, pos);
|
||||||
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
||||||
}
|
}
|
||||||
if (pos == remaining)
|
if (pos == remaining)
|
||||||
@@ -488,7 +498,8 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// streaming input: no bulk fast path
|
/// streaming input: no bulk fast path
|
||||||
char_int_type get_after_string_run(std::false_type /*bulk*/)
|
template<bool Keep>
|
||||||
|
char_int_type get_after_string_run(std::false_type /*bulk*/, std::integral_constant<bool, Keep> /*keep*/)
|
||||||
{
|
{
|
||||||
return get();
|
return get();
|
||||||
}
|
}
|
||||||
@@ -502,13 +513,18 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
may contain \0 bytes), and token_buffer.size() is the number of bytes in the
|
may contain \0 bytes), and token_buffer.size() is the number of bytes in the
|
||||||
string.
|
string.
|
||||||
|
|
||||||
|
@param[in] keep whether the string is copied into token_buffer; if false
|
||||||
|
(accept(), see scan()), it is only checked, with the same
|
||||||
|
errors, and token_buffer stays empty
|
||||||
|
|
||||||
@return token_type::value_string if string could be successfully scanned,
|
@return token_type::value_string if string could be successfully scanned,
|
||||||
token_type::parse_error otherwise
|
token_type::parse_error otherwise
|
||||||
|
|
||||||
@note In case of errors, variable error_message contains a textual
|
@note In case of errors, variable error_message contains a textual
|
||||||
description.
|
description.
|
||||||
*/
|
*/
|
||||||
token_type scan_string()
|
template<bool Keep>
|
||||||
|
token_type scan_string(std::integral_constant<bool, Keep> keep)
|
||||||
{
|
{
|
||||||
// reset token_buffer (ignore opening quote)
|
// reset token_buffer (ignore opening quote)
|
||||||
reset();
|
reset();
|
||||||
@@ -520,7 +536,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
{
|
{
|
||||||
// bulk-consume ordinary characters from contiguous input, then
|
// bulk-consume ordinary characters from contiguous input, then
|
||||||
// get the next character, the one that ends the run
|
// get the next character, the one that ends the run
|
||||||
switch (get_after_string_run(std::integral_constant<bool, bulk_scan>()))
|
switch (get_after_string_run(std::integral_constant<bool, bulk_scan>(), keep))
|
||||||
{
|
{
|
||||||
// end of file while parsing the string
|
// end of file while parsing the string
|
||||||
case char_traits<char_type>::eof():
|
case char_traits<char_type>::eof():
|
||||||
@@ -542,35 +558,35 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
{
|
{
|
||||||
// quotation mark
|
// quotation mark
|
||||||
case '\"':
|
case '\"':
|
||||||
add('\"');
|
add_string_char(keep, '\"');
|
||||||
break;
|
break;
|
||||||
// reverse solidus
|
// reverse solidus
|
||||||
case '\\':
|
case '\\':
|
||||||
add('\\');
|
add_string_char(keep, '\\');
|
||||||
break;
|
break;
|
||||||
// solidus
|
// solidus
|
||||||
case '/':
|
case '/':
|
||||||
add('/');
|
add_string_char(keep, '/');
|
||||||
break;
|
break;
|
||||||
// backspace
|
// backspace
|
||||||
case 'b':
|
case 'b':
|
||||||
add('\b');
|
add_string_char(keep, '\b');
|
||||||
break;
|
break;
|
||||||
// form feed
|
// form feed
|
||||||
case 'f':
|
case 'f':
|
||||||
add('\f');
|
add_string_char(keep, '\f');
|
||||||
break;
|
break;
|
||||||
// line feed
|
// line feed
|
||||||
case 'n':
|
case 'n':
|
||||||
add('\n');
|
add_string_char(keep, '\n');
|
||||||
break;
|
break;
|
||||||
// carriage return
|
// carriage return
|
||||||
case 'r':
|
case 'r':
|
||||||
add('\r');
|
add_string_char(keep, '\r');
|
||||||
break;
|
break;
|
||||||
// tab
|
// tab
|
||||||
case 't':
|
case 't':
|
||||||
add('\t');
|
add_string_char(keep, '\t');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// unicode escapes
|
// unicode escapes
|
||||||
@@ -638,10 +654,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF);
|
JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF);
|
||||||
|
|
||||||
// translate codepoint into bytes
|
// translate codepoint into bytes
|
||||||
encode_utf8(static_cast<std::uint32_t>(codepoint), [this](std::uint32_t byte)
|
add_codepoint(keep, codepoint);
|
||||||
{
|
|
||||||
add(static_cast<char_int_type>(byte));
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -824,7 +837,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
case 0x7E:
|
case 0x7E:
|
||||||
case 0x7F:
|
case 0x7F:
|
||||||
{
|
{
|
||||||
add(current);
|
add_string_char(keep, current);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -860,7 +873,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
case 0xDE:
|
case 0xDE:
|
||||||
case 0xDF:
|
case 0xDF:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF})))
|
if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range(keep, {0x80, 0xBF})))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -870,7 +883,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// U+0800..U+0FFF: bytes E0 A0..BF 80..BF
|
// U+0800..U+0FFF: bytes E0 A0..BF 80..BF
|
||||||
case 0xE0:
|
case 0xE0:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0xA0, 0xBF, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -894,7 +907,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
case 0xEE:
|
case 0xEE:
|
||||||
case 0xEF:
|
case 0xEF:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0xBF, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -904,7 +917,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// U+D000..U+D7FF: bytes ED 80..9F 80..BF
|
// U+D000..U+D7FF: bytes ED 80..9F 80..BF
|
||||||
case 0xED:
|
case 0xED:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0x9F, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -914,7 +927,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
|
// U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
|
||||||
case 0xF0:
|
case 0xF0:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -926,7 +939,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
case 0xF2:
|
case 0xF2:
|
||||||
case 0xF3:
|
case 0xF3:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -936,7 +949,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// U+100000..U+10FFFF F4 80..8F 80..BF 80..BF
|
// U+100000..U+10FFFF F4 80..8F 80..BF 80..BF
|
||||||
case 0xF4:
|
case 0xF4:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF}))))
|
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF}))))
|
||||||
{
|
{
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
@@ -1398,7 +1411,7 @@ scan_number_done:
|
|||||||
return token_type::value_unsigned;
|
return token_type::value_unsigned;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (number_type == token_type::value_integer)
|
else // value_integer (the callers handle value_float themselves)
|
||||||
{
|
{
|
||||||
if (parse_integer_signed(first, last, value_integer))
|
if (parse_integer_signed(first, last, value_integer))
|
||||||
{
|
{
|
||||||
@@ -1427,7 +1440,7 @@ scan_number_done:
|
|||||||
token_type convert_number(token_type number_type, std::size_t mantissa_end)
|
token_type convert_number(token_type number_type, std::size_t mantissa_end)
|
||||||
{
|
{
|
||||||
// accept() only needs to know whether the input is valid, so it sets
|
// accept() only needs to know whether the input is valid, so it sets
|
||||||
// discard_number_values (see json.hpp), and an integer token whose
|
// discard_values (see json.hpp), and an integer token whose
|
||||||
// digit count shows that it fits is reported without calling
|
// digit count shows that it fits is reported without calling
|
||||||
// convert_integer(). A number with up to 18 digits always fits into
|
// convert_integer(). A number with up to 18 digits always fits into
|
||||||
// both std::uint64_t and std::int64_t (18 nines is about 1e18, below
|
// both std::uint64_t and std::int64_t (18 nines is about 1e18, below
|
||||||
@@ -1442,7 +1455,7 @@ scan_number_done:
|
|||||||
// json_sax_acceptor, whose number callbacks discard their argument and
|
// json_sax_acceptor, whose number callbacks discard their argument and
|
||||||
// return true, and the parser rejects neither integers nor finite
|
// return true, and the parser rejects neither integers nor finite
|
||||||
// floats. value_unsigned/value_integer are left unset here, so a caller
|
// floats. value_unsigned/value_integer are left unset here, so a caller
|
||||||
// that reads the converted value must not set discard_number_values.
|
// that reads the converted value must not set discard_values.
|
||||||
//
|
//
|
||||||
// On contiguous input, scan_number_bulk_contiguous() converts integer
|
// On contiguous input, scan_number_bulk_contiguous() converts integer
|
||||||
// tokens itself and does not pass them to this function, unless
|
// tokens itself and does not pass them to this function, unless
|
||||||
@@ -1450,7 +1463,7 @@ scan_number_done:
|
|||||||
// reached for input without bulk access (e.g. streams), with
|
// reached for input without bulk access (e.g. streams), with
|
||||||
// JSON_DIAGNOSTIC_POSITIONS, or when scan_number_bulk_contiguous()
|
// JSON_DIAGNOSTIC_POSITIONS, or when scan_number_bulk_contiguous()
|
||||||
// falls back to scan_number().
|
// falls back to scan_number().
|
||||||
if (discard_number_values)
|
if (discard_values())
|
||||||
{
|
{
|
||||||
constexpr std::size_t safe_digit_count = 18;
|
constexpr std::size_t safe_digit_count = 18;
|
||||||
if (number_type == token_type::value_unsigned && token_buffer.size() <= safe_digit_count)
|
if (number_type == token_type::value_unsigned && token_buffer.size() <= safe_digit_count)
|
||||||
@@ -1504,11 +1517,9 @@ scan_number_done:
|
|||||||
*/
|
*/
|
||||||
token_type scan_number_bulk_contiguous()
|
token_type scan_number_bulk_contiguous()
|
||||||
{
|
{
|
||||||
// a pending unget offsets the buffer position from current; fall back
|
// called right after scan_after_whitespace() read current (see
|
||||||
if (next_unget)
|
// get_after_string_run()), so no unget is pending
|
||||||
{
|
JSON_ASSERT(!next_unget);
|
||||||
return token_type::uninitialized;
|
|
||||||
}
|
|
||||||
const std::size_t rem = ia.bulk_remaining();
|
const std::size_t rem = ia.bulk_remaining();
|
||||||
if (rem == 0)
|
if (rem == 0)
|
||||||
{
|
{
|
||||||
@@ -1529,11 +1540,7 @@ scan_number_done:
|
|||||||
if (data[0] == '-')
|
if (data[0] == '-')
|
||||||
{
|
{
|
||||||
number_type = token_type::value_integer;
|
number_type = token_type::value_integer;
|
||||||
i = 1;
|
i = 1; // avail >= 2: rem == 0 returned above
|
||||||
if (i >= avail)
|
|
||||||
{
|
|
||||||
return token_type::uninitialized;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const std::size_t integer_begin = i;
|
const std::size_t integer_begin = i;
|
||||||
if (data[i] == '0')
|
if (data[i] == '0')
|
||||||
@@ -1701,7 +1708,7 @@ scan_number_done:
|
|||||||
// w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a
|
// w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a
|
||||||
// value that underflows is a finite number as well. Only a token that
|
// value that underflows is a finite number as well. Only a token that
|
||||||
// may overflow has to be converted to find out.
|
// may overflow has to be converted to find out.
|
||||||
if (JSON_HEDLEY_UNLIKELY(discard_number_values && s.exponent + 19 <= std::numeric_limits<number_float_t>::max_exponent10))
|
if (JSON_HEDLEY_UNLIKELY(discard_values() && s.exponent + 19 <= std::numeric_limits<number_float_t>::max_exponent10))
|
||||||
{
|
{
|
||||||
value_float = 0;
|
value_float = 0;
|
||||||
return true;
|
return true;
|
||||||
@@ -1775,7 +1782,10 @@ scan_number_done:
|
|||||||
/// the error at the right character
|
/// the error at the right character
|
||||||
JSON_HEDLEY_ALWAYS_INLINE bool scan_literal_bulk(const char* rest, const std::size_t length, std::true_type /*bulk*/)
|
JSON_HEDLEY_ALWAYS_INLINE bool scan_literal_bulk(const char* rest, const std::size_t length, std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
if (next_unget || ia.bulk_remaining() < length
|
// called right after scan_after_whitespace() read current (see
|
||||||
|
// get_after_string_run()), so no unget is pending
|
||||||
|
JSON_ASSERT(!next_unget);
|
||||||
|
if (ia.bulk_remaining() < length
|
||||||
|| std::memcmp(ia.bulk_data(), rest, length) != 0)
|
|| std::memcmp(ia.bulk_data(), rest, length) != 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -2033,6 +2043,34 @@ scan_number_done:
|
|||||||
token_buffer.push_back(static_cast<typename string_t::value_type>(c));
|
token_buffer.push_back(static_cast<typename string_t::value_type>(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// add a character of a string token; accept() checks the string but does
|
||||||
|
/// not keep its value (@a keep is false)
|
||||||
|
void add_string_char(std::true_type /*keep*/, char_int_type c)
|
||||||
|
{
|
||||||
|
token_buffer.push_back(static_cast<typename string_t::value_type>(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_string_char(std::false_type /*keep*/, char_int_type /*c*/) noexcept {}
|
||||||
|
|
||||||
|
/// add the UTF-8 bytes of a code point from a \\u escape to a string token
|
||||||
|
void add_codepoint(std::true_type /*keep*/, int codepoint)
|
||||||
|
{
|
||||||
|
encode_utf8(static_cast<std::uint32_t>(codepoint), [this](std::uint32_t byte)
|
||||||
|
{
|
||||||
|
add(static_cast<char_int_type>(byte));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_codepoint(std::false_type /*keep*/, int /*codepoint*/) noexcept {}
|
||||||
|
|
||||||
|
/// append a run of ordinary string characters (see get_after_string_run())
|
||||||
|
void append_string_run(std::true_type /*keep*/, const unsigned char* data, std::size_t n)
|
||||||
|
{
|
||||||
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void append_string_run(std::false_type /*keep*/, const unsigned char* /*data*/, std::size_t /*n*/) noexcept {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/////////////////////
|
/////////////////////
|
||||||
// value getters
|
// value getters
|
||||||
@@ -2427,7 +2465,9 @@ scan_number_done:
|
|||||||
|
|
||||||
// string
|
// string
|
||||||
case '\"':
|
case '\"':
|
||||||
return scan_string();
|
// accept() (DiscardValues) checks strings without copying them
|
||||||
|
// into token_buffer
|
||||||
|
return scan_string(keep_strings {});
|
||||||
|
|
||||||
// number
|
// number
|
||||||
case '-':
|
case '-':
|
||||||
@@ -2512,11 +2552,19 @@ scan_number_done:
|
|||||||
std::size_t decimal_point_position = std::string::npos;
|
std::size_t decimal_point_position = std::string::npos;
|
||||||
|
|
||||||
/// whether the caller only needs the token types and never looks at the
|
/// whether the caller only needs the token types and never looks at the
|
||||||
/// converted numeric values; set only by accept(), which parses through
|
/// values of number and string tokens; DiscardValues, set only by accept(), which parses
|
||||||
/// json_sax_acceptor. When set, convert_number() skips converting integer
|
/// through json_sax_acceptor. When set, convert_number() skips converting
|
||||||
/// tokens whose digit count guarantees that they fit into 64 bits (see
|
/// integer tokens whose digit count guarantees that they fit into 64 bits
|
||||||
/// there)
|
/// (see there), floats are converted only when they may overflow (see
|
||||||
const bool discard_number_values = false;
|
/// convert_accumulated_float()), and strings are checked without being
|
||||||
|
/// copied into token_buffer (see scan())
|
||||||
|
static bool discard_values() noexcept
|
||||||
|
{
|
||||||
|
return DiscardValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the tag of scan_string(): whether string tokens are kept
|
||||||
|
using keep_strings = typename std::conditional<DiscardValues, std::false_type, std::true_type>::type;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|||||||
@@ -72,14 +72,14 @@ This class implements a parser for JSON text. Nested arrays and objects are trac
|
|||||||
stack instead of recursion, so deeply nested input does not exhaust the call stack, and what is read
|
stack instead of recursion, so deeply nested input does not exhaust the call stack, and what is read
|
||||||
is reported as SAX events.
|
is reported as SAX events.
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType, bool DiscardValues = false>
|
||||||
class parser
|
class parser
|
||||||
{
|
{
|
||||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||||
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||||
using number_float_t = typename BasicJsonType::number_float_t;
|
using number_float_t = typename BasicJsonType::number_float_t;
|
||||||
using string_t = typename BasicJsonType::string_t;
|
using string_t = typename BasicJsonType::string_t;
|
||||||
using lexer_t = lexer<BasicJsonType, InputAdapterType>;
|
using lexer_t = lexer<BasicJsonType, InputAdapterType, DiscardValues>;
|
||||||
using token_type = typename lexer_t::token_type;
|
using token_type = typename lexer_t::token_type;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -88,10 +88,9 @@ class parser
|
|||||||
parser_callback_t<BasicJsonType> cb = nullptr,
|
parser_callback_t<BasicJsonType> cb = nullptr,
|
||||||
const bool allow_exceptions_ = true,
|
const bool allow_exceptions_ = true,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
const bool ignore_trailing_commas_ = false,
|
const bool ignore_trailing_commas_ = false)
|
||||||
const bool discard_number_values_ = false)
|
|
||||||
: callback(std::move(cb))
|
: callback(std::move(cb))
|
||||||
, m_lexer(std::move(adapter), ignore_comments, discard_number_values_)
|
, m_lexer(std::move(adapter), ignore_comments)
|
||||||
, allow_exceptions(allow_exceptions_)
|
, allow_exceptions(allow_exceptions_)
|
||||||
, ignore_trailing_commas(ignore_trailing_commas_)
|
, ignore_trailing_commas(ignore_trailing_commas_)
|
||||||
{
|
{
|
||||||
@@ -574,14 +573,7 @@ class parser
|
|||||||
|
|
||||||
std::string exception_message(const token_type expected, const std::string& context)
|
std::string exception_message(const token_type expected, const std::string& context)
|
||||||
{
|
{
|
||||||
std::string error_msg = "syntax error ";
|
std::string error_msg = concat("syntax error while parsing ", context, " - ");
|
||||||
|
|
||||||
if (!context.empty())
|
|
||||||
{
|
|
||||||
error_msg += concat("while parsing ", context, ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
error_msg += "- ";
|
|
||||||
|
|
||||||
if (last_token == token_type::parse_error)
|
if (last_token == token_type::parse_error)
|
||||||
{
|
{
|
||||||
@@ -599,7 +591,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
return error_msg;
|
return error_msg;
|
||||||
}
|
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// callback function
|
/// callback function
|
||||||
|
|||||||
Loaded 100 of 182 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user