Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e46a982be4 | ||
|
|
73b0a3aba3 | ||
|
|
7196bc5b22 | ||
|
|
2ac0851157 | ||
|
|
f093573828 | ||
|
|
2301f01284 | ||
|
|
056ba3b7c1 | ||
|
|
d7b9472173 | ||
|
|
04a5accfb9 |
No files matched your search
@@ -24,7 +24,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
actions: write
|
actions: write
|
||||||
|
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
dependency-review:
|
dependency-review:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ concurrency:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ci_test_gcc:
|
ci_test_gcc:
|
||||||
runs-on: ubuntu-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
# 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-26.04
|
runs-on: ubuntu-latest
|
||||||
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-26.04
|
runs-on: ubuntu-latest
|
||||||
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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -344,8 +344,8 @@ class binary_reader
|
|||||||
nested document could declare a length that disagrees with where its
|
nested document could declare a length that disagrees with where its
|
||||||
terminator actually falls and quietly hand the bytes in between to the
|
terminator actually falls and quietly hand the bytes in between to the
|
||||||
enclosing document. A well-formed document is at least 5 bytes (the prefix
|
enclosing document. A well-formed document is at least 5 bytes (the prefix
|
||||||
plus the terminator); open_bson_document() rejects a smaller size before
|
plus the terminator); the equality also rejects those impossible sizes,
|
||||||
it is stored, so @a document_size is never negative here.
|
since at least 5 bytes are always consumed.
|
||||||
|
|
||||||
@param[in] document_start value of chars_read before the size prefix
|
@param[in] document_start value of chars_read before the size prefix
|
||||||
@param[in] document_size the declared document size
|
@param[in] document_size the declared document size
|
||||||
@@ -353,7 +353,7 @@ class binary_reader
|
|||||||
*/
|
*/
|
||||||
bool check_bson_document_size(const std::size_t document_start, const std::int32_t document_size)
|
bool check_bson_document_size(const std::size_t document_start, const std::int32_t document_size)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(document_size) != chars_read - document_start))
|
if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast<std::size_t>(document_size) != chars_read - document_start))
|
||||||
{
|
{
|
||||||
return bson_document_size_error(document_size, chars_read - document_start);
|
return bson_document_size_error(document_size, chars_read - document_start);
|
||||||
}
|
}
|
||||||
@@ -467,13 +467,11 @@ 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,
|
// an array's elements are named "0", "1", ... in the wire format,
|
||||||
// and those names are not passed on: they are skipped, and only
|
// and those names are not passed on: they are skipped unless
|
||||||
// checked if error_handler_t::strict must reject ill-formed UTF-8
|
// error_handler_t::strict must reject ill-formed UTF-8 in them
|
||||||
const bool is_object = container_stack.back().is_object;
|
const bool is_object = container_stack.back().is_object;
|
||||||
key.clear();
|
key.clear();
|
||||||
if (JSON_HEDLEY_UNLIKELY(!(is_object || error_handler != error_handler_t::strict
|
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key, 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;
|
||||||
}
|
}
|
||||||
@@ -497,7 +495,7 @@ class binary_reader
|
|||||||
@param[in] store whether the characters are needed; if not (the names of
|
@param[in] store whether the characters are needed; if not (the names of
|
||||||
an array's elements), they are skipped and @a result may
|
an array's elements), they are skipped and @a result may
|
||||||
be left unchanged, and they are not checked against
|
be left unchanged, and they are not checked against
|
||||||
@ref error_handler (@ref check_bson_cstr checks them)
|
@ref error_handler
|
||||||
@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.
|
||||||
*/
|
*/
|
||||||
@@ -556,42 +554,6 @@ 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*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
|
bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
|
||||||
{
|
{
|
||||||
@@ -3084,13 +3046,16 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
if (size_and_type.second != 0)
|
if (size_and_type.second != 0)
|
||||||
{
|
{
|
||||||
for (std::size_t i = 0; i < size_and_type.first; ++i)
|
if (size_and_type.second != 'N')
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, size_and_type.second)))
|
for (std::size_t i = 0; i < size_and_type.first; ++i)
|
||||||
{
|
{
|
||||||
return false;
|
if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, size_and_type.second)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dim.push_back(dimlen);
|
||||||
}
|
}
|
||||||
dim.push_back(dimlen);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -3292,9 +3257,11 @@ class binary_reader
|
|||||||
// follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order
|
// follow the documented _ArrayType_, _ArraySize_, _ArrayData_ order
|
||||||
if (ndarray_dtype != 0)
|
if (ndarray_dtype != 0)
|
||||||
{
|
{
|
||||||
// every optimized type marker is_valid_optimized_type() accepts for BJData has a name
|
|
||||||
const char* type_name = bjd_type_name(ndarray_dtype);
|
const char* type_name = bjd_type_name(ndarray_dtype);
|
||||||
JSON_ASSERT(type_name != nullptr);
|
if (JSON_HEDLEY_UNLIKELY(type_name == nullptr))
|
||||||
|
{
|
||||||
|
return unexpected_byte("invalid byte", "type");
|
||||||
|
}
|
||||||
|
|
||||||
string_t type_key = "_ArrayType_";
|
string_t type_key = "_ArrayType_";
|
||||||
string_t type = type_name; // sax->string() takes a reference
|
string_t type = type_name; // sax->string() takes a reference
|
||||||
@@ -3377,6 +3344,12 @@ class binary_reader
|
|||||||
if (current == '$')
|
if (current == '$')
|
||||||
{
|
{
|
||||||
result.second = get(); // must not ignore 'N', because 'N' maybe the type
|
result.second = get(); // must not ignore 'N', because 'N' maybe the type
|
||||||
|
if (input_format == input_format_t::bjdata
|
||||||
|
&& JSON_HEDLEY_UNLIKELY(is_bjd_excluded_optimized_type(result.second)))
|
||||||
|
{
|
||||||
|
return optimized_type_error();
|
||||||
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type")))
|
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type")))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -3404,7 +3377,7 @@ class binary_reader
|
|||||||
// an ndarray was read here only if the flag flipped; when it was
|
// an ndarray was read here only if the flag flipped; when it was
|
||||||
// seeded true, get_ubjson_size_value() already rejected the nested
|
// seeded true, get_ubjson_size_value() already rejected the nested
|
||||||
// dimension vector
|
// dimension vector
|
||||||
if (is_ndarray && !inside_ndarray)
|
if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray)
|
||||||
{
|
{
|
||||||
result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters
|
result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters
|
||||||
}
|
}
|
||||||
@@ -3414,7 +3387,7 @@ class binary_reader
|
|||||||
if (current == '#')
|
if (current == '#')
|
||||||
{
|
{
|
||||||
const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
|
const bool is_error = get_ubjson_size_value(result.first, is_ndarray);
|
||||||
if (is_ndarray && !inside_ndarray)
|
if (input_format == input_format_t::bjdata && is_ndarray && !inside_ndarray)
|
||||||
{
|
{
|
||||||
return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size");
|
return last_byte_error(exception_id::unexpected_byte, "ndarray requires both type and size", "size");
|
||||||
}
|
}
|
||||||
@@ -3588,6 +3561,7 @@ class binary_reader
|
|||||||
JSON_HEDLEY_NEVER_INLINE
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
bool parse_ubjson_typed_run(std::size_t& remaining, const char_int_type type, std::true_type /*bulk*/)
|
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)
|
switch (type)
|
||||||
{
|
{
|
||||||
case 'U':
|
case 'U':
|
||||||
@@ -3601,11 +3575,11 @@ class binary_reader
|
|||||||
case 'L':
|
case 'L':
|
||||||
return parse_ubjson_typed_run<std::int64_t>(remaining);
|
return parse_ubjson_typed_run<std::int64_t>(remaining);
|
||||||
case 'u':
|
case 'u':
|
||||||
return parse_ubjson_typed_run<std::uint16_t>(remaining);
|
return !bjdata || parse_ubjson_typed_run<std::uint16_t>(remaining);
|
||||||
case 'm':
|
case 'm':
|
||||||
return parse_ubjson_typed_run<std::uint32_t>(remaining);
|
return !bjdata || parse_ubjson_typed_run<std::uint32_t>(remaining);
|
||||||
case 'M':
|
case 'M':
|
||||||
return parse_ubjson_typed_run<std::uint64_t>(remaining);
|
return !bjdata || parse_ubjson_typed_run<std::uint64_t>(remaining);
|
||||||
case 'd':
|
case 'd':
|
||||||
return parse_ubjson_typed_run<float>(remaining);
|
return parse_ubjson_typed_run<float>(remaining);
|
||||||
case 'D':
|
case 'D':
|
||||||
@@ -3686,7 +3660,7 @@ class binary_reader
|
|||||||
// if bit-8 of size_and_type.second is set to 1, encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata):
|
// if bit-8 of size_and_type.second is set to 1, encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata):
|
||||||
// {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]}
|
// {"_ArrayType_" : "typeid", "_ArraySize_" : [n1, n2, ...], "_ArrayData_" : [v1, v2, ...]}
|
||||||
|
|
||||||
if ((size_and_type.second & (1 << 8)) != 0)
|
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
|
||||||
{
|
{
|
||||||
size_and_type.second &= ~(static_cast<char_int_type>(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker
|
size_and_type.second &= ~(static_cast<char_int_type>(1) << 8); // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker
|
||||||
|
|
||||||
@@ -3745,7 +3719,7 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If BJData type marker is 'B' decode as binary
|
// If BJData type marker is 'B' decode as binary
|
||||||
if (size_and_type.first != npos && size_and_type.second == 'B')
|
if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B')
|
||||||
{
|
{
|
||||||
binary_t result;
|
binary_t result;
|
||||||
return get_binary(size_and_type.first, result) && sax->binary(result);
|
return get_binary(size_and_type.first, result) && sax->binary(result);
|
||||||
@@ -3794,7 +3768,7 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do not accept ND-array size in objects in BJData
|
// do not accept ND-array size in objects in BJData
|
||||||
if ((size_and_type.second & (1 << 8)) != 0)
|
if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)
|
||||||
{
|
{
|
||||||
return last_byte_error(exception_id::unexpected_byte, "BJData object does not support ND-array size in optimized format", "object");
|
return last_byte_error(exception_id::unexpected_byte, "BJData object does not support ND-array size in optimized format", "object");
|
||||||
}
|
}
|
||||||
@@ -5075,9 +5049,8 @@ class binary_reader
|
|||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
/*!
|
/*!
|
||||||
@brief check whether @a marker is a type marker that may follow '$'
|
@brief check whether @a marker is a type marker that may follow '$'
|
||||||
UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C.
|
UBJSON: Z N T F i U I l L d D H C S [ {; BJData: the numeric markers and C
|
||||||
The markers BJData excludes (F H N S T Z [ {) are the ones
|
(BJData's excluded markers are rejected separately).
|
||||||
binary_writer::is_bjdata_excluded_type_marker() lists; keep the two in sync.
|
|
||||||
*/
|
*/
|
||||||
bool is_valid_optimized_type(const char_int_type marker) const noexcept
|
bool is_valid_optimized_type(const char_int_type marker) const noexcept
|
||||||
{
|
{
|
||||||
@@ -5112,6 +5085,20 @@ class binary_reader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief whether @a marker is excluded from BJData's optimized ND-array types
|
||||||
|
@return whether @a marker is one of 'F', 'H', 'N', 'S', 'T', 'Z', '[', '{'
|
||||||
|
|
||||||
|
Mirrors binary_writer's @ref binary_writer::is_bjdata_excluded_type_marker
|
||||||
|
"is_bjdata_excluded_type_marker()`, which encodes the same list the other
|
||||||
|
way; keep the two in sync.
|
||||||
|
*/
|
||||||
|
static constexpr bool is_bjd_excluded_optimized_type(const char_int_type marker) noexcept
|
||||||
|
{
|
||||||
|
return marker == '[' || marker == '{' || marker == 'S' || marker == 'H'
|
||||||
|
|| marker == 'T' || marker == 'F' || marker == 'N' || marker == 'Z';
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief look up the ND-array element type name for a BJData dtype marker
|
@brief look up the ND-array element type name for a BJData dtype marker
|
||||||
@return the type name ("uint8", "int8", ...), or nullptr if @a marker does
|
@return the type name ("uint8", "int8", ...), or nullptr if @a marker does
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -1737,7 +1737,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 +1794,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 +1960,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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,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;
|
||||||
}
|
}
|
||||||
@@ -437,11 +439,10 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
template<bool Keep>
|
template<bool Keep>
|
||||||
char_int_type get_after_string_run(std::true_type /*bulk*/, std::integral_constant<bool, Keep> 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)
|
||||||
{
|
{
|
||||||
@@ -1410,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))
|
||||||
{
|
{
|
||||||
@@ -1516,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)
|
||||||
{
|
{
|
||||||
@@ -1541,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')
|
||||||
@@ -1787,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;
|
||||||
|
|||||||
@@ -575,10 +575,7 @@ class parser
|
|||||||
{
|
{
|
||||||
std::string error_msg = "syntax error ";
|
std::string error_msg = "syntax error ";
|
||||||
|
|
||||||
if (!context.empty())
|
error_msg += concat("while parsing ", context, ' ');
|
||||||
{
|
|
||||||
error_msg += concat("while parsing ", context, ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
error_msg += "- ";
|
error_msg += "- ";
|
||||||
|
|
||||||
@@ -598,7 +595,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
return error_msg;
|
return error_msg;
|
||||||
}
|
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// callback function
|
/// callback function
|
||||||
|
|||||||
@@ -108,6 +108,9 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
|
// m_it is already value-initialized, so this is not needed for
|
||||||
|
// correctness; it is kept because GCC generates faster loops with it
|
||||||
|
// (iterating a parsed document is 0.5-2.7 % slower without it, #6005)
|
||||||
if (m_object->is_object())
|
if (m_object->is_object())
|
||||||
{
|
{
|
||||||
m_it.object_iterator = typename object_t::iterator();
|
m_it.object_iterator = typename object_t::iterator();
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ class json_pointer
|
|||||||
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
||||||
result.reference_tokens.insert(result.reference_tokens.end(), rhs.reference_tokens.begin(), rhs.reference_tokens.end());
|
result.reference_tokens.insert(result.reference_tokens.end(), rhs.reference_tokens.begin(), rhs.reference_tokens.end());
|
||||||
return result;
|
return result;
|
||||||
}
|
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||||
|
|
||||||
/// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer
|
/// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer
|
||||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
||||||
@@ -315,7 +315,7 @@ class json_pointer
|
|||||||
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
||||||
result.reference_tokens.push_back(std::move(token));
|
result.reference_tokens.push_back(std::move(token));
|
||||||
return result;
|
return result;
|
||||||
}
|
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||||
|
|
||||||
/// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer
|
/// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer
|
||||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
||||||
@@ -2180,7 +2180,7 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
// create an entry in the array
|
// create an entry in the array
|
||||||
current = ¤t->operator[](array_index<BasicJsonType>(token));
|
current = ¤t->operator[](array_index<BasicJsonType>(token));
|
||||||
break;
|
break; // LCOV_EXCL_LINE (needs an array index with 10 or more digits: more than 1e9 elements)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -2217,7 +2217,7 @@ class json_pointer
|
|||||||
json_pointer<string_t> result;
|
json_pointer<string_t> result;
|
||||||
result.reference_tokens = reference_tokens;
|
result.reference_tokens = reference_tokens;
|
||||||
return result;
|
return result;
|
||||||
}
|
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||||
|
|
||||||
json_pointer<string_t> convert()&&
|
json_pointer<string_t> convert()&&
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -833,7 +833,7 @@ class binary_writer
|
|||||||
while (n != 0);
|
while (n != 0);
|
||||||
std::reverse(result.begin(), result.end());
|
std::reverse(result.begin(), result.end());
|
||||||
return result;
|
return result;
|
||||||
}
|
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief the decimal digits of the integer @a n, with a leading '-' if it
|
@brief the decimal digits of the integer @a n, with a leading '-' if it
|
||||||
@@ -860,7 +860,7 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
std::reverse(result.begin(), result.end());
|
std::reverse(result.begin(), result.end());
|
||||||
return result;
|
return result;
|
||||||
}
|
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief throws because the integer @a j is too large for @a format
|
@brief throws because the integer @a j is too large for @a format
|
||||||
@@ -1792,7 +1792,7 @@ class binary_writer
|
|||||||
std::memcpy(out, &n, sizeof(NumberType));
|
std::memcpy(out, &n, sizeof(NumberType));
|
||||||
if (!is_little_endian)
|
if (!is_little_endian)
|
||||||
{
|
{
|
||||||
std::reverse(out, out + sizeof(NumberType));
|
std::reverse(out, out + sizeof(NumberType)); // LCOV_EXCL_LINE (big-endian hosts only)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1921,7 +1921,7 @@ class binary_writer
|
|||||||
const string_t& value = *j.m_data.m_value.string;
|
const string_t& value = *j.m_data.m_value.string;
|
||||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(value.size() + 1ul)))
|
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(value.size() + 1ul)))
|
||||||
{
|
{
|
||||||
return false;
|
return false; // LCOV_EXCL_LINE (a string of 2 GiB or more)
|
||||||
}
|
}
|
||||||
out[0] = to_char_type(0x02);
|
out[0] = to_char_type(0x02);
|
||||||
store_little_endian<std::int32_t>(p, static_cast<std::int32_t>(value.size() + 1ul));
|
store_little_endian<std::int32_t>(p, static_cast<std::int32_t>(value.size() + 1ul));
|
||||||
@@ -2378,7 +2378,7 @@ class binary_writer
|
|||||||
const std::size_t size = oa.written() - pos;
|
const std::size_t size = oa.written() - pos;
|
||||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
|
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
|
||||||
{
|
{
|
||||||
return false;
|
return false; // LCOV_EXCL_LINE (a document of 2 GiB or more)
|
||||||
}
|
}
|
||||||
const auto le_size = static_cast<std::uint32_t>(size);
|
const auto le_size = static_cast<std::uint32_t>(size);
|
||||||
oa.overwrite(pos, is_little_endian ? le_size : byteswap(le_size));
|
oa.overwrite(pos, is_little_endian ? le_size : byteswap(le_size));
|
||||||
@@ -2507,7 +2507,7 @@ class binary_writer
|
|||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(nested_size_pos)))
|
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(nested_size_pos)))
|
||||||
{
|
{
|
||||||
return false;
|
return false; // LCOV_EXCL_LINE (a document of 2 GiB or more)
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2522,7 +2522,7 @@ class binary_writer
|
|||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(current.size_slot)))
|
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(current.size_slot)))
|
||||||
{
|
{
|
||||||
return false;
|
return false; // LCOV_EXCL_LINE (a document of 2 GiB or more)
|
||||||
}
|
}
|
||||||
if (parents.empty())
|
if (parents.empty())
|
||||||
{
|
{
|
||||||
@@ -2779,10 +2779,10 @@ class binary_writer
|
|||||||
|
|
||||||
Selected through masks rather than branches, as the type and the sign of
|
Selected through masks rather than branches, as the type and the sign of
|
||||||
the values in an array of mixed signs alternate unpredictably (non-negative
|
the values in an array of mixed signs alternate unpredictably (non-negative
|
||||||
integers are number_unsigned after parsing). Up to 64 bits, the sign is bit
|
integers are number_unsigned after parsing). The sign is bit 63 of the
|
||||||
63 of the result (a narrower integer is sign-extended); computing it with
|
result (a narrower integer is sign-extended); computing it with masks keeps
|
||||||
masks keeps GCC from branching on the type. A wider integer has to be
|
GCC from branching on the type. Only called if @ref integers_fit_64_bits();
|
||||||
compared.
|
a wider number type is written with range checks on the whole value.
|
||||||
*/
|
*/
|
||||||
static std::uint64_t integer_bits(const BasicJsonType& j, std::uint64_t& sign_mask) noexcept
|
static std::uint64_t integer_bits(const BasicJsonType& j, std::uint64_t& sign_mask) noexcept
|
||||||
{
|
{
|
||||||
@@ -2790,9 +2790,7 @@ class binary_writer
|
|||||||
const std::uint64_t raw = is_signed
|
const std::uint64_t raw = is_signed
|
||||||
? static_cast<std::uint64_t>(j.m_data.m_value.number_integer)
|
? static_cast<std::uint64_t>(j.m_data.m_value.number_integer)
|
||||||
: static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned);
|
: static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned);
|
||||||
sign_mask = sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
sign_mask = (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u));
|
||||||
? (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u))
|
|
||||||
: std::uint64_t{0} - static_cast<std::uint64_t>(is_signed && j.m_data.m_value.number_integer < 0);
|
|
||||||
return raw;
|
return raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3129,19 +3127,22 @@ class binary_writer
|
|||||||
// negative fixnum
|
// negative fixnum
|
||||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||||
}
|
}
|
||||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)())
|
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
|
||||||
|
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
||||||
{
|
{
|
||||||
// int 8
|
// int 8
|
||||||
oa.write_character(to_char_type(0xD0));
|
oa.write_character(to_char_type(0xD0));
|
||||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||||
}
|
}
|
||||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)())
|
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
|
||||||
|
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
||||||
{
|
{
|
||||||
// int 16
|
// int 16
|
||||||
oa.write_character(to_char_type(0xD1));
|
oa.write_character(to_char_type(0xD1));
|
||||||
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
||||||
}
|
}
|
||||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)())
|
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
|
||||||
|
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
||||||
{
|
{
|
||||||
// int 32
|
// int 32
|
||||||
oa.write_character(to_char_type(0xD2));
|
oa.write_character(to_char_type(0xD2));
|
||||||
@@ -4057,7 +4058,7 @@ class binary_writer
|
|||||||
Containers, strings, high-precision numbers, booleans and null cannot be
|
Containers, strings, high-precision numbers, booleans and null cannot be
|
||||||
declared as the single type of an optimized container in BJData; such a
|
declared as the single type of an optimized container in BJData; such a
|
||||||
container is written unoptimized. The reader rejects them with the same
|
container is written unoptimized. The reader rejects them with the same
|
||||||
list (binary_reader::is_valid_optimized_type()).
|
list (binary_reader::is_bjd_excluded_optimized_type()).
|
||||||
*/
|
*/
|
||||||
static constexpr bool is_bjdata_excluded_type_marker(const CharType marker) noexcept
|
static constexpr bool is_bjdata_excluded_type_marker(const CharType marker) noexcept
|
||||||
{
|
{
|
||||||
@@ -4156,57 +4157,34 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief validate (dry_run) or write one BJData ND-array element of integer dtype @a T
|
@brief whether the BJData ND-array element @a el can be written with dtype 'd' (single precision)
|
||||||
@return whether @a el's value is in range of @a T; always true when @a dry_run is false
|
|
||||||
*/
|
|
||||||
template<typename T>
|
|
||||||
bool write_bjdata_ndarray_element(const BasicJsonType& el, const bool dry_run)
|
|
||||||
{
|
|
||||||
if (dry_run)
|
|
||||||
{
|
|
||||||
return bjdata_ndarray_value_in_range<T>(el);
|
|
||||||
}
|
|
||||||
using storage_type = typename std::conditional<std::is_unsigned<T>::value, std::uint64_t, std::int64_t>::type;
|
|
||||||
write_number(static_cast<T>(el.template get<storage_type>()), true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief validate (dry_run) or write one BJData ND-array element of dtype 'd' (single precision)
|
|
||||||
@return whether @a el's value survives narrowing to float and back without any change
|
@return whether @a el's value survives narrowing to float and back without any change
|
||||||
(so the ND-array round-trips exactly), or is infinite or NaN; always true when
|
(so the ND-array round-trips exactly), or is infinite or NaN
|
||||||
@a dry_run is false
|
|
||||||
*/
|
*/
|
||||||
bool write_bjdata_ndarray_float_element(const BasicJsonType& el, const bool dry_run)
|
static bool bjdata_ndarray_float_in_range(const BasicJsonType& el) noexcept
|
||||||
{
|
{
|
||||||
const auto dval = static_cast<double>(el.m_data.m_value.number_float);
|
const auto dval = static_cast<double>(el.m_data.m_value.number_float);
|
||||||
if (dry_run)
|
|
||||||
{
|
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||||
#endif
|
#endif
|
||||||
// a value that would be rounded (rather than exactly represented) by the
|
// a value that would be rounded (rather than exactly represented) by the
|
||||||
// narrowing to float is treated like an out-of-range integer element; this
|
// narrowing to float is treated like an out-of-range integer element; this
|
||||||
// is the same criterion write_compact_float() uses for CBOR/MessagePack
|
// is the same criterion write_compact_float() uses for CBOR/MessagePack
|
||||||
const bool in_range = std::isnan(dval) ||
|
return std::isnan(dval) ||
|
||||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
dval <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
||||||
static_cast<double>(static_cast<float>(dval)) == dval) ||
|
static_cast<double>(static_cast<float>(dval)) == dval) ||
|
||||||
std::isinf(dval);
|
std::isinf(dval);
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
return in_range;
|
|
||||||
}
|
|
||||||
write_number(static_cast<float>(dval), true);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief write the elements of an ND-array as the number kind @a dtype names
|
@brief write the elements of an ND-array as the number kind @a dtype names
|
||||||
(see @ref write_bjdata_ndarray), after they have been found to be
|
(see @ref write_bjdata_ndarray), after they have been found to be
|
||||||
in range by a dry run of @ref write_bjdata_ndarray_elements
|
in range by @ref bjdata_ndarray_elements_in_range
|
||||||
|
|
||||||
The elements are encoded in blocks into space reserved from the sink.
|
The elements are encoded in blocks into space reserved from the sink.
|
||||||
*/
|
*/
|
||||||
@@ -4288,20 +4266,13 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief validate or write every element of a BJData ND-array's `_ArrayData_`
|
@brief whether every element of a BJData ND-array's `_ArrayData_` is in range for its dtype
|
||||||
@param[in] array_data the `_ArrayData_` array
|
@param[in] array_data the `_ArrayData_` array
|
||||||
@param[in] dtype the ND-array dtype marker, as returned by bjdata_ndarray_type_marker()
|
@param[in] dtype the ND-array dtype marker, as returned by bjdata_ndarray_type_marker()
|
||||||
@param[in] dry_run true to only range-check each element, false to write it
|
@return whether every element is in range for @a dtype
|
||||||
@return whether every element is in range for @a dtype (always true when @a dry_run is false)
|
|
||||||
*/
|
*/
|
||||||
bool write_bjdata_ndarray_elements(const BasicJsonType& array_data, const CharType dtype, const bool dry_run)
|
static bool bjdata_ndarray_elements_in_range(const BasicJsonType& array_data, const CharType dtype)
|
||||||
{
|
{
|
||||||
if (!dry_run)
|
|
||||||
{
|
|
||||||
write_bjdata_ndarray_values(array_data, dtype);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& el : array_data)
|
for (const auto& el : array_data)
|
||||||
{
|
{
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
@@ -4309,43 +4280,39 @@ class binary_writer
|
|||||||
{
|
{
|
||||||
case 'U':
|
case 'U':
|
||||||
case 'B':
|
case 'B':
|
||||||
ok = write_bjdata_ndarray_element<std::uint8_t>(el, dry_run);
|
ok = bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
||||||
break;
|
break;
|
||||||
case 'C':
|
case 'C':
|
||||||
// BJData limits a char to 0..127 (the intersection of the int8 and uint8 ranges)
|
// BJData limits a char to 0..127 (the intersection of the int8 and uint8 ranges)
|
||||||
ok = bjdata_ndarray_value_in_range<std::int8_t>(el) && bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
ok = bjdata_ndarray_value_in_range<std::int8_t>(el) && bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
||||||
break;
|
break;
|
||||||
case 'i':
|
case 'i':
|
||||||
ok = write_bjdata_ndarray_element<std::int8_t>(el, dry_run);
|
ok = bjdata_ndarray_value_in_range<std::int8_t>(el);
|
||||||
break;
|
break;
|
||||||
case 'u':
|
case 'u':
|
||||||
ok = write_bjdata_ndarray_element<std::uint16_t>(el, dry_run);
|
ok = bjdata_ndarray_value_in_range<std::uint16_t>(el);
|
||||||
break;
|
break;
|
||||||
case 'I':
|
case 'I':
|
||||||
ok = write_bjdata_ndarray_element<std::int16_t>(el, dry_run);
|
ok = bjdata_ndarray_value_in_range<std::int16_t>(el);
|
||||||
break;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
ok = write_bjdata_ndarray_element<std::uint32_t>(el, dry_run);
|
ok = bjdata_ndarray_value_in_range<std::uint32_t>(el);
|
||||||
break;
|
break;
|
||||||
case 'l':
|
case 'l':
|
||||||
ok = write_bjdata_ndarray_element<std::int32_t>(el, dry_run);
|
ok = bjdata_ndarray_value_in_range<std::int32_t>(el);
|
||||||
break;
|
break;
|
||||||
case 'M':
|
case 'M':
|
||||||
ok = write_bjdata_ndarray_element<std::uint64_t>(el, dry_run);
|
ok = bjdata_ndarray_value_in_range<std::uint64_t>(el);
|
||||||
break;
|
break;
|
||||||
case 'L':
|
case 'L':
|
||||||
ok = write_bjdata_ndarray_element<std::int64_t>(el, dry_run);
|
ok = bjdata_ndarray_value_in_range<std::int64_t>(el);
|
||||||
break;
|
break;
|
||||||
case 'd':
|
case 'd':
|
||||||
ok = write_bjdata_ndarray_float_element(el, dry_run);
|
ok = bjdata_ndarray_float_in_range(el);
|
||||||
break;
|
break;
|
||||||
case 'D':
|
case 'D':
|
||||||
default:
|
default:
|
||||||
// 'D' (double) already spans the full range of number_float_t
|
// 'D' (double) already spans the full range of number_float_t
|
||||||
if (!dry_run)
|
|
||||||
{
|
|
||||||
write_number(el.template get<double>(), true);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!ok)
|
if (!ok)
|
||||||
@@ -4432,7 +4399,7 @@ class binary_writer
|
|||||||
const auto dim = el.template get<std::uint64_t>();
|
const auto dim = el.template get<std::uint64_t>();
|
||||||
if (!value_in_range_of<std::size_t>(dim))
|
if (!value_in_range_of<std::size_t>(dim))
|
||||||
{
|
{
|
||||||
return true;
|
return true; // LCOV_EXCL_LINE (size_t is narrower than 64 bits)
|
||||||
}
|
}
|
||||||
const auto dim_size = static_cast<std::size_t>(dim);
|
const auto dim_size = static_cast<std::size_t>(dim);
|
||||||
|
|
||||||
@@ -4482,7 +4449,7 @@ class binary_writer
|
|||||||
// wrap (integers) or overflow to infinity (the "single" precision
|
// wrap (integers) or overflow to infinity (the "single" precision
|
||||||
// float) instead of being reported, so such an object falls back to
|
// float) instead of being reported, so such an object falls back to
|
||||||
// a plain object encoding as well
|
// a plain object encoding as well
|
||||||
if (!write_bjdata_ndarray_elements(array_data, dtype, true))
|
if (!bjdata_ndarray_elements_in_range(array_data, dtype))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -4494,7 +4461,7 @@ class binary_writer
|
|||||||
|
|
||||||
write_ubjson(array_size, use_count, use_type, true, true, bjdata_version);
|
write_ubjson(array_size, use_count, use_type, true, true, bjdata_version);
|
||||||
|
|
||||||
write_bjdata_ndarray_elements(array_data, dtype, false);
|
write_bjdata_ndarray_values(array_data, dtype);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4820,8 +4787,8 @@ class binary_writer
|
|||||||
run are encoded into space reserved from the sink, as in
|
run are encoded into space reserved from the sink, as in
|
||||||
@ref write_array_elements; an integer that BON8 cannot write (an unsigned
|
@ref write_array_elements; an integer that BON8 cannot write (an unsigned
|
||||||
one above the int64 range, or one of a number type wider than 64 bits) throws
|
one above the int64 range, or one of a number type wider than 64 bits) throws
|
||||||
from write_bon8_value, like any other value that is not a number. Kept out
|
out_of_range.407, as write_bon8_value does. Kept out of line, like
|
||||||
of line, like @ref write_cbor_array.
|
@ref write_cbor_array.
|
||||||
*/
|
*/
|
||||||
template<typename ArrayType>
|
template<typename ArrayType>
|
||||||
JSON_HEDLEY_NEVER_INLINE
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
@@ -4871,10 +4838,10 @@ class binary_writer
|
|||||||
string_open = false;
|
string_open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unencodable)
|
if (JSON_HEDLEY_UNLIKELY(unencodable))
|
||||||
{
|
{
|
||||||
write_bon8_value(*it, string_open, depth + 1);
|
// an integer beyond the int64 range, as write_bon8_value would report it
|
||||||
++it;
|
throw_integer_out_of_range(*it, input_format_t::bon8, "int64");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4909,14 +4876,13 @@ class binary_writer
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case value_t::number_integer:
|
case value_t::number_integer:
|
||||||
if (JSON_HEDLEY_LIKELY(integer_fits<std::int64_t>(value.m_data.m_value.number_integer)))
|
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(value.m_data.m_value.number_integer)))
|
||||||
{
|
{
|
||||||
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_integer));
|
// beyond 64 bits (number types wider than that)
|
||||||
string_open = false;
|
throw_integer_out_of_range(value, input_format_t::bon8, "int64");
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
// beyond 64 bits (number types wider than that): throws
|
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_integer));
|
||||||
write_bon8_value(value, string_open, depth + 1);
|
string_open = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
@@ -4925,14 +4891,13 @@ class binary_writer
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case value_t::number_unsigned:
|
case value_t::number_unsigned:
|
||||||
if (JSON_HEDLEY_LIKELY(integer_fits_max<std::int64_t>(value.m_data.m_value.number_unsigned)))
|
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::int64_t>(value.m_data.m_value.number_unsigned)))
|
||||||
{
|
{
|
||||||
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_unsigned));
|
// too large for BON8
|
||||||
string_open = false;
|
throw_integer_out_of_range(value, input_format_t::bon8, "int64");
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
// too large for BON8: throws
|
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_unsigned));
|
||||||
write_bon8_value(value, string_open, depth + 1);
|
string_open = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
|
|||||||
@@ -1113,36 +1113,21 @@ class serializer
|
|||||||
{
|
{
|
||||||
// the code point is at least 0x60 (smaller ones are handled
|
// the code point is at least 0x60 (smaller ones are handled
|
||||||
// above) and is not printable ASCII (which the scanner
|
// above) and is not printable ASCII (which the scanner
|
||||||
// copies): it is DEL (0x7F) under EnsureAscii or a non-ASCII
|
// copies). Under EnsureAscii it is DEL (0x7F): every
|
||||||
// character
|
// well-formed multi-byte character is escaped by
|
||||||
|
// write_u_escapes() above, so decode() only ever accepts a
|
||||||
|
// single byte here, and nothing of it was buffered
|
||||||
if (EnsureAscii)
|
if (EnsureAscii)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(codepoint >= 0x7F);
|
JSON_ASSERT(codepoint == 0x7F);
|
||||||
if (error_handler == error_handler_t::keep)
|
JSON_ASSERT(bytes == bytes_after_last_accept);
|
||||||
{
|
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
||||||
// this character was buffered as raw bytes below
|
|
||||||
// in case it turned out to be part of an
|
|
||||||
// ill-formed sequence (which is kept as is); now
|
|
||||||
// that it decoded to a well-formed code point,
|
|
||||||
// undo that and \u-escape it like any other
|
|
||||||
// character instead
|
|
||||||
bytes = bytes_after_last_accept;
|
|
||||||
}
|
|
||||||
if (codepoint <= 0xFFFF)
|
|
||||||
{
|
|
||||||
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
|
||||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// copy byte to buffer (all previous bytes of the
|
// copy byte to buffer (all previous bytes of the
|
||||||
// sequence have been copied in the default case below)
|
// sequence have been copied in the default case below)
|
||||||
string_buffer[bytes++] = s[i];
|
string_buffer[bytes++] = s[i]; // LCOV_EXCL_LINE (well-formed sequences are copied in runs)
|
||||||
}
|
}
|
||||||
|
|
||||||
reserve_string_buffer(bytes);
|
reserve_string_buffer(bytes);
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ inline OutStringType concat(Args && ... args)
|
|||||||
str.reserve(concat_length(args...));
|
str.reserve(concat_length(args...));
|
||||||
concat_into(str, std::forward<Args>(args)...);
|
concat_into(str, std::forward<Args>(args)...);
|
||||||
return str;
|
return str;
|
||||||
}
|
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -83,10 +83,6 @@ template<typename StringType>
|
|||||||
inline void unescape(StringType& s)
|
inline void unescape(StringType& s)
|
||||||
{
|
{
|
||||||
auto pos = s.find_first_of('~', 0);
|
auto pos = s.find_first_of('~', 0);
|
||||||
if (pos == StringType::npos)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
StringType result;
|
StringType result;
|
||||||
result.reserve(s.size());
|
result.reserve(s.size());
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ StringType to_string(std::size_t value)
|
|||||||
StringType result;
|
StringType result;
|
||||||
int_to_string(result, value);
|
int_to_string(result, value);
|
||||||
return result;
|
return result;
|
||||||
}
|
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||||
|
|
||||||
/// @return a byte as two uppercase hexadecimal digits
|
/// @return a byte as two uppercase hexadecimal digits
|
||||||
inline std::string hex_byte(const std::uint8_t byte)
|
inline std::string hex_byte(const std::uint8_t byte)
|
||||||
@@ -46,7 +46,7 @@ inline std::string hex_byte(const std::uint8_t byte)
|
|||||||
result[0] = nibble_to_hex[byte / 16];
|
result[0] = nibble_to_hex[byte / 16];
|
||||||
result[1] = nibble_to_hex[byte % 16];
|
result[1] = nibble_to_hex[byte % 16];
|
||||||
return result;
|
return result;
|
||||||
}
|
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief write a UTF-16 code unit as four lowercase hexadecimal digits
|
@brief write a UTF-16 code unit as four lowercase hexadecimal digits
|
||||||
@@ -390,7 +390,7 @@ inline StringType sanitize_utf8(const StringType& s, const error_handler_t error
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -1174,8 +1174,9 @@ indent_done:
|
|||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
|
else if (!negative || NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
|
||||||
{
|
{
|
||||||
|
// (without a sign, the dispatch saw a digit)
|
||||||
p = skip_digits(p + 1, e);
|
p = skip_digits(p + 1, e);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -397,9 +397,9 @@ class view_side
|
|||||||
return m_view.size();
|
return m_view.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
string_view_t string() const
|
string_view_t string() const noexcept
|
||||||
{
|
{
|
||||||
return m_view.get_string();
|
return {m_view.m_doc->str(*m_view.m_node), m_view.m_node->len};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool boolean() const noexcept
|
bool boolean() const noexcept
|
||||||
@@ -428,28 +428,19 @@ class view_side
|
|||||||
return string_view_t(m_view.m_doc->str(*m_view.m_node), m_view.m_node->len);
|
return string_view_t(m_view.m_doc->str(*m_view.m_node), m_view.m_node->len);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// a number, boolean, or null as a basic_json value (no allocation)
|
/// a number as a basic_json value (no allocation); only called for numbers
|
||||||
BasicJsonType scalar() const
|
BasicJsonType scalar() const
|
||||||
{
|
{
|
||||||
switch (m_view.type())
|
const value_t t = m_view.type();
|
||||||
|
if (t == value_t::number_integer)
|
||||||
{
|
{
|
||||||
case value_t::number_integer:
|
return BasicJsonType(m_view.template get<typename BasicJsonType::number_integer_t>());
|
||||||
return BasicJsonType(m_view.template get<typename BasicJsonType::number_integer_t>());
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
return BasicJsonType(m_view.template get<typename BasicJsonType::number_unsigned_t>());
|
|
||||||
case value_t::number_float:
|
|
||||||
return BasicJsonType(m_view.template get<typename BasicJsonType::number_float_t>());
|
|
||||||
case value_t::boolean:
|
|
||||||
return BasicJsonType(m_view.template get<bool>());
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::object:
|
|
||||||
case value_t::array:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
return BasicJsonType(nullptr);
|
|
||||||
}
|
}
|
||||||
|
if (t == value_t::number_unsigned)
|
||||||
|
{
|
||||||
|
return BasicJsonType(m_view.template get<typename BasicJsonType::number_unsigned_t>());
|
||||||
|
}
|
||||||
|
return BasicJsonType(m_view.template get<typename BasicJsonType::number_float_t>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// start at the first element of this array
|
/// start at the first element of this array
|
||||||
@@ -531,9 +522,9 @@ class json_side
|
|||||||
return m_json->size();
|
return m_json->size();
|
||||||
}
|
}
|
||||||
|
|
||||||
string_view_t string() const
|
string_view_t string() const noexcept
|
||||||
{
|
{
|
||||||
const auto& s = m_json->template get_ref<const typename BasicJsonType::string_t&>();
|
const auto& s = *m_json->template get_ptr<const typename BasicJsonType::string_t*>();
|
||||||
return string_view_t(s.data(), s.size());
|
return string_view_t(s.data(), s.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,11 +96,11 @@ NLOHMANN_VIEW_NOINLINE inline void check_utf8_scan(const unsigned char* p, std::
|
|||||||
}
|
}
|
||||||
if (*q >= 0x80)
|
if (*q >= 0x80)
|
||||||
{
|
{
|
||||||
check_utf8_bytewise(p, n, static_cast<std::size_t>(q - p));
|
break; // ill-formed UTF-8: reported below
|
||||||
return; // not reached: the scan stopped at ill-formed UTF-8
|
|
||||||
}
|
}
|
||||||
++q; // a quote, backslash, or control character
|
++q; // a quote, backslash, or control character
|
||||||
}
|
}
|
||||||
|
check_utf8_bytewise(p, n, static_cast<std::size_t>(q - p));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void check_utf8(const char* s, std::size_t n)
|
inline void check_utf8(const char* s, std::size_t n)
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ inline void set_moved(document_data& d, node* n, node* seq, std::size_t cap)
|
|||||||
e.moved_cap[n->off] = cap;
|
e.moved_cap[n->off] = cap;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
reserve_moved(d);
|
// every caller has called reserve_moved() (see above), so neither push_back() can throw
|
||||||
e.moved.push_back(seq);
|
e.moved.push_back(seq);
|
||||||
e.moved_cap.push_back(cap);
|
e.moved_cap.push_back(cap);
|
||||||
n->off = static_cast<std::uint32_t>(e.moved.size() - 1);
|
n->off = static_cast<std::uint32_t>(e.moved.size() - 1);
|
||||||
@@ -243,7 +243,7 @@ inline node* find_parent(const document_data& d, const node* target)
|
|||||||
}
|
}
|
||||||
if (target == lo)
|
if (target == lo)
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr; // LCOV_EXCL_LINE (the root, or a new value's root, which is linked)
|
||||||
}
|
}
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -185,10 +185,6 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
|
|||||||
end = p.end;
|
end = p.end;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// the open brackets: n, then the last element as long as it is a non-empty container
|
// the open brackets: n, then the last element as long as it is a non-empty container
|
||||||
std::size_t closing = 0;
|
std::size_t closing = 0;
|
||||||
std::size_t pos = std::string::npos;
|
std::size_t pos = std::string::npos;
|
||||||
@@ -420,10 +416,12 @@ struct throwing_sax
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LCOV_EXCL_START (throw_parse_failure parses JSON only)
|
||||||
bool binary(binary_t& /*unused*/)
|
bool binary(binary_t& /*unused*/)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
bool start_object(std::size_t /*unused*/)
|
bool start_object(std::size_t /*unused*/)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -787,10 +787,6 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
|
|||||||
d.edits.reset();
|
d.edits.reset();
|
||||||
d.base[2] = nullptr;
|
d.base[2] = nullptr;
|
||||||
d.owned.clear();
|
d.owned.clear();
|
||||||
if (d.owned_image.empty() || image != d.owned_image.data())
|
|
||||||
{
|
|
||||||
d.owned_image.clear();
|
|
||||||
}
|
|
||||||
d.arena.clear();
|
d.arena.clear();
|
||||||
d.indexes.clear();
|
d.indexes.clear();
|
||||||
d.index_slots.clear();
|
d.index_slots.clear();
|
||||||
|
|||||||
@@ -65,14 +65,9 @@ struct position_source
|
|||||||
return token{end - start};
|
return token{end - start};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the range [first, last), or none if either is npos
|
/// the range [first, last)
|
||||||
void set(std::size_t first, std::size_t last) noexcept
|
void set(std::size_t first, std::size_t last) noexcept
|
||||||
{
|
{
|
||||||
if (first == std::string::npos || last == std::string::npos)
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
start = first;
|
start = first;
|
||||||
end = last;
|
end = last;
|
||||||
}
|
}
|
||||||
@@ -191,11 +186,11 @@ struct position_source
|
|||||||
first = n.off;
|
first = n.off;
|
||||||
last = n.off + 4u;
|
last = n.off + 4u;
|
||||||
break;
|
break;
|
||||||
case node_kind::object:
|
case node_kind::object: // LCOV_EXCL_LINE
|
||||||
case node_kind::array:
|
case node_kind::array:
|
||||||
case node_kind::link:
|
case node_kind::link:
|
||||||
default:
|
default:
|
||||||
break;
|
break; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
// (a crafted image can point past the text)
|
// (a crafted image can point past the text)
|
||||||
if (last == std::string::npos || last > d.size)
|
if (last == std::string::npos || last > d.size)
|
||||||
|
|||||||
@@ -641,7 +641,7 @@ class view_serializer
|
|||||||
{
|
{
|
||||||
invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8)
|
invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8)
|
||||||
}
|
}
|
||||||
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
|
write_escaped(s[i]); // a quote, a backslash, or a control character
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
m_out.put('"');
|
m_out.put('"');
|
||||||
@@ -1389,20 +1389,10 @@ class view_serializer
|
|||||||
/// a float node as dump() writes it
|
/// a float node as dump() writes it
|
||||||
void write_float_node(const node& n)
|
void write_float_node(const node& n)
|
||||||
{
|
{
|
||||||
write_float_node(n, fast_double {});
|
// (only for types other than double: doubles go through write_double_ahead())
|
||||||
}
|
|
||||||
|
|
||||||
void write_float_node(const node& n, std::false_type /*other*/)
|
|
||||||
{
|
|
||||||
write_float(float_value<number_float_t>(m_doc, n));
|
write_float(float_value<number_float_t>(m_doc, n));
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_float_node(const node& n, std::true_type /*double*/)
|
|
||||||
{
|
|
||||||
m_out.reserve(64);
|
|
||||||
m_out.set_cursor(write_double_at(m_out.cursor(), n));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief (doubles) the float at n as dump() writes it, at w (64 bytes of room)
|
@brief (doubles) the float at n as dump() writes it, at w (64 bytes of room)
|
||||||
|
|
||||||
@@ -1497,10 +1487,11 @@ class view_serializer
|
|||||||
{
|
{
|
||||||
const string_t dumped = BasicJsonType(string_t(reinterpret_cast<const char*>(s), n)).dump(); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
const string_t dumped = BasicJsonType(string_t(reinterpret_cast<const char*>(s), n)).dump(); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
static_cast<void>(dumped);
|
static_cast<void>(dumped);
|
||||||
}
|
} // LCOV_EXCL_LINE (dump() throws: the callers pass only ill-formed UTF-8)
|
||||||
|
|
||||||
template<bool EnsureAscii>
|
/// write the escape sequence of a quote, a backslash or a control
|
||||||
void write_codepoint(std::uint32_t codepoint, const unsigned char* bytes, std::size_t len)
|
/// character (the only bytes a run of plain output stops at)
|
||||||
|
void write_escaped(std::uint32_t codepoint)
|
||||||
{
|
{
|
||||||
switch (codepoint)
|
switch (codepoint)
|
||||||
{
|
{
|
||||||
@@ -1528,20 +1519,7 @@ class view_serializer
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (codepoint <= 0x1F || (EnsureAscii && codepoint >= 0x7F))
|
write_u_escape(codepoint); // (a control character)
|
||||||
{
|
|
||||||
if (codepoint <= 0xFFFF)
|
|
||||||
{
|
|
||||||
write_u_escape(codepoint);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_u_escape(0xD7C0u + (codepoint >> 10u));
|
|
||||||
write_u_escape(0xDC00u + (codepoint & 0x3FFu));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_out.put(reinterpret_cast<const char*>(bytes), len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) LCOV_EXCL_LINE (printable characters are copied in runs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_u_escape(std::uint32_t u)
|
void write_u_escape(std::uint32_t u)
|
||||||
|
|||||||
+28
-23
@@ -400,7 +400,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}};
|
result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}};
|
||||||
#elif defined(__GNUC__) || defined(__GNUG__)
|
#elif defined(__GNUC__) || defined(__GNUG__)
|
||||||
result["compiler"] = {{"family", "gcc"}, {"version", detail::concat<string_t>(
|
result["compiler"] = {{"family", "gcc"}, {"version", detail::concat<string_t>(
|
||||||
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC__)), '.',
|
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC__)), '.', // LCOV_EXCL_LINE
|
||||||
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC_MINOR__)), '.',
|
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC_MINOR__)), '.',
|
||||||
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC_PATCHLEVEL__)))
|
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC_PATCHLEVEL__)))
|
||||||
}
|
}
|
||||||
@@ -672,14 +672,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
binary = create<binary_t>();
|
binary = create<binary_t>();
|
||||||
break;
|
break;
|
||||||
case value_t::null:
|
case value_t::null: // LCOV_EXCL_LINE
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
case value_t::number_integer:
|
case value_t::number_integer:
|
||||||
case value_t::number_unsigned:
|
case value_t::number_unsigned:
|
||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
break; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1034,14 +1034,14 @@ public:
|
|||||||
destroy_container(t);
|
destroy_container(t);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case value_t::null:
|
case value_t::null: // LCOV_EXCL_LINE
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
case value_t::number_integer:
|
case value_t::number_integer:
|
||||||
case value_t::number_unsigned:
|
case value_t::number_unsigned:
|
||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
break;
|
break; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1214,9 +1214,9 @@ public:
|
|||||||
return is_array() ? array_capacity() : object_capacity();
|
return is_array() ? array_capacity() : object_capacity();
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
static constexpr std::size_t container_capacity() noexcept
|
static constexpr std::size_t container_capacity() noexcept // LCOV_EXCL_LINE
|
||||||
{
|
{
|
||||||
return detail::unknown_size();
|
return detail::unknown_size(); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1402,7 +1402,7 @@ public:
|
|||||||
/// into this switch is worth a few percent when copying a value made
|
/// into this switch is worth a few percent when copying a value made
|
||||||
/// mostly of numbers
|
/// mostly of numbers
|
||||||
JSON_HEDLEY_ALWAYS_INLINE
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
static void copy_leaf_value(const basic_json& src, basic_json& dst)
|
static void copy_leaf_value(const basic_json& src, basic_json& dst) // LCOV_EXCL_LINE
|
||||||
{
|
{
|
||||||
switch (src.m_data.m_type)
|
switch (src.m_data.m_type)
|
||||||
{
|
{
|
||||||
@@ -1576,7 +1576,7 @@ public:
|
|||||||
auto src_it = src_object.cbegin();
|
auto src_it = src_object.cbegin();
|
||||||
for (auto& element : *dst.m_data.m_value.object)
|
for (auto& element : *dst.m_data.m_value.object)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_LIKELY(src_it != src_object.cend() && copy_keys_equal(src_it->first, element.first)))
|
if (JSON_HEDLEY_LIKELY(copy_keys_equal(src_it->first, element.first)))
|
||||||
{
|
{
|
||||||
copy_shallow(src_it->second, element.second, worklist);
|
copy_shallow(src_it->second, element.second, worklist);
|
||||||
++src_it;
|
++src_it;
|
||||||
@@ -5353,8 +5353,7 @@ public:
|
|||||||
/// @name lexicographical comparison operators
|
/// @name lexicographical comparison operators
|
||||||
/// @{
|
/// @{
|
||||||
|
|
||||||
// the same-type cases for non-owning scalars, shared by JSON_IMPLEMENT_OPERATOR
|
// the same-type cases for non-owning scalars, used by JSON_IMPLEMENT_SCALAR_FAST
|
||||||
// and JSON_IMPLEMENT_SCALAR_FAST
|
|
||||||
#define JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
|
#define JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
|
||||||
case value_t::null: \
|
case value_t::null: \
|
||||||
return (null_result); \
|
return (null_result); \
|
||||||
@@ -5376,7 +5375,7 @@ public:
|
|||||||
// because any negative signed value is smaller than any unsigned value.
|
// because any negative signed value is smaller than any unsigned value.
|
||||||
// Otherwise, the non-negative signed value is cast to unsigned before the
|
// Otherwise, the non-negative signed value is cast to unsigned before the
|
||||||
// comparison to avoid wraparound.
|
// comparison to avoid wraparound.
|
||||||
#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result, deep_result, may_descend) \
|
#define JSON_IMPLEMENT_OPERATOR(op, unordered_result, default_result, deep_result, may_descend) \
|
||||||
const auto lhs_type = lhs.type(); \
|
const auto lhs_type = lhs.type(); \
|
||||||
const auto rhs_type = rhs.type(); \
|
const auto rhs_type = rhs.type(); \
|
||||||
\
|
\
|
||||||
@@ -5404,8 +5403,15 @@ public:
|
|||||||
return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \
|
return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
|
/* same-type scalars never get here: the inline fast path of the operator handles them */ \
|
||||||
\
|
case value_t::null: \
|
||||||
|
case value_t::boolean: \
|
||||||
|
case value_t::number_integer: \
|
||||||
|
case value_t::number_unsigned: \
|
||||||
|
case value_t::number_float: \
|
||||||
|
JSON_ASSERT(false); /* NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) */ \
|
||||||
|
return (unordered_result); \
|
||||||
|
\
|
||||||
case value_t::string: \
|
case value_t::string: \
|
||||||
return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.string); \
|
return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.string); \
|
||||||
\
|
\
|
||||||
@@ -5445,7 +5451,7 @@ public:
|
|||||||
? (number_integer_t(-1) op number_integer_t(1)) \
|
? (number_integer_t(-1) op number_integer_t(1)) \
|
||||||
: (static_cast<number_unsigned_t>(lhs.m_data.m_value.number_integer) op rhs.m_data.m_value.number_unsigned); \
|
: (static_cast<number_unsigned_t>(lhs.m_data.m_value.number_integer) op rhs.m_data.m_value.number_unsigned); \
|
||||||
} \
|
} \
|
||||||
else if(compares_unordered(lhs, rhs))\
|
else if(lhs_type == value_t::discarded || rhs_type == value_t::discarded)\
|
||||||
{\
|
{\
|
||||||
return (unordered_result);\
|
return (unordered_result);\
|
||||||
}\
|
}\
|
||||||
@@ -5513,7 +5519,7 @@ public:
|
|||||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||||
#endif
|
#endif
|
||||||
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
|
JSON_IMPLEMENT_OPERATOR( ==, false, false,
|
||||||
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
|
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
@@ -5527,7 +5533,6 @@ public:
|
|||||||
// default_result is used if we cannot compare values. In that case,
|
// default_result is used if we cannot compare values. In that case,
|
||||||
// we compare types.
|
// we compare types.
|
||||||
JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*
|
JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*
|
||||||
std::partial_ordering::equivalent,
|
|
||||||
std::partial_ordering::unordered,
|
std::partial_ordering::unordered,
|
||||||
lhs_type <=> rhs_type, // *NOPAD*
|
lhs_type <=> rhs_type, // *NOPAD*
|
||||||
to_partial_ordering(compare_iteratively<true>(lhs, rhs, false)), true)
|
to_partial_ordering(compare_iteratively<true>(lhs, rhs, false)), true)
|
||||||
@@ -5539,7 +5544,7 @@ public:
|
|||||||
// default_result is used if we cannot compare values. In that case,
|
// default_result is used if we cannot compare values. In that case,
|
||||||
// we compare types. Note we have to call the operator explicitly,
|
// we compare types. Note we have to call the operator explicitly,
|
||||||
// because MSVC has problems otherwise.
|
// because MSVC has problems otherwise.
|
||||||
JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type),
|
JSON_IMPLEMENT_OPERATOR( <, false, operator<(lhs_type, rhs_type),
|
||||||
compare_iteratively<true>(lhs, rhs, true) == compare_result::less, false)
|
compare_iteratively<true>(lhs, rhs, true) == compare_result::less, false)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -5554,7 +5559,7 @@ public:
|
|||||||
/// their inlined code at each call site small. Binary values are
|
/// their inlined code at each call site small. Binary values are
|
||||||
/// rare and left to equal_general().
|
/// rare and left to equal_general().
|
||||||
JSON_HEDLEY_ALWAYS_INLINE
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
static int equal_leaf(const_reference lhs, const_reference rhs) noexcept
|
static int equal_leaf(const_reference lhs, const_reference rhs) noexcept // LCOV_EXCL_LINE
|
||||||
{
|
{
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
@@ -6863,7 +6868,7 @@ public:
|
|||||||
basic_json result(value_t::object);
|
basic_json result(value_t::object);
|
||||||
json_pointer::flatten("", *this, result);
|
json_pointer::flatten("", *this, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||||
|
|
||||||
/// @brief unflatten a previously flattened JSON value
|
/// @brief unflatten a previously flattened JSON value
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/unflatten/
|
/// @sa https://json.nlohmann.me/api/basic_json/unflatten/
|
||||||
@@ -7576,7 +7581,7 @@ public:
|
|||||||
result.append(path.data(), path.size());
|
result.append(path.data(), path.size());
|
||||||
diff_append_key(result, key);
|
diff_append_key(result, key);
|
||||||
return result;
|
return result;
|
||||||
}
|
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||||
|
|
||||||
/// @brief the path of the element @a index of the value at @a path
|
/// @brief the path of the element @a index of the value at @a path
|
||||||
static string_t diff_index_path(const string_t& path, const std::size_t index)
|
static string_t diff_index_path(const string_t& path, const std::size_t index)
|
||||||
@@ -7586,7 +7591,7 @@ public:
|
|||||||
result.append(path.data(), path.size());
|
result.append(path.data(), path.size());
|
||||||
diff_append_index(result, index);
|
diff_append_index(result, index);
|
||||||
return result;
|
return result;
|
||||||
}
|
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||||
|
|
||||||
/// @brief append the "remove" operations for the elements of array
|
/// @brief append the "remove" operations for the elements of array
|
||||||
/// @a source from @a index on, and the "add" operations for the
|
/// @a source from @a index on, and the "add" operations for the
|
||||||
@@ -7746,7 +7751,7 @@ public:
|
|||||||
// operations
|
// operations
|
||||||
if (source == target)
|
if (source == target)
|
||||||
{
|
{
|
||||||
return false;
|
return false; // LCOV_EXCL_LINE (equal objects with the same members in the same order return early)
|
||||||
}
|
}
|
||||||
|
|
||||||
// slow path: the common keys are in a different relative
|
// slow path: the common keys are in a different relative
|
||||||
|
|||||||
@@ -1705,9 +1705,11 @@ class basic_json_document
|
|||||||
{
|
{
|
||||||
// (as before: the library's adapter decides what a null stream yields)
|
// (as before: the library's adapter decides what a null stream yields)
|
||||||
detail::view::append_adapter(buf, detail::input_adapter(file));
|
detail::view::append_adapter(buf, detail::input_adapter(file));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
detail::view::append_file(buf, file);
|
else
|
||||||
|
{
|
||||||
|
detail::view::append_file(buf, file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
Generated
+192
-264
File diff suppressed because it is too large.
Load diff
Generated
+35
-74
@@ -2479,8 +2479,9 @@ indent_done:
|
|||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
|
else if (!negative || NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
|
||||||
{
|
{
|
||||||
|
// (without a sign, the dispatch saw a digit)
|
||||||
p = skip_digits(p + 1, e);
|
p = skip_digits(p + 1, e);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -3549,9 +3550,9 @@ class view_side
|
|||||||
return m_view.size();
|
return m_view.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
string_view_t string() const
|
string_view_t string() const noexcept
|
||||||
{
|
{
|
||||||
return m_view.get_string();
|
return {m_view.m_doc->str(*m_view.m_node), m_view.m_node->len};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool boolean() const noexcept
|
bool boolean() const noexcept
|
||||||
@@ -3580,28 +3581,19 @@ class view_side
|
|||||||
return string_view_t(m_view.m_doc->str(*m_view.m_node), m_view.m_node->len);
|
return string_view_t(m_view.m_doc->str(*m_view.m_node), m_view.m_node->len);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// a number, boolean, or null as a basic_json value (no allocation)
|
/// a number as a basic_json value (no allocation); only called for numbers
|
||||||
BasicJsonType scalar() const
|
BasicJsonType scalar() const
|
||||||
{
|
{
|
||||||
switch (m_view.type())
|
const value_t t = m_view.type();
|
||||||
|
if (t == value_t::number_integer)
|
||||||
{
|
{
|
||||||
case value_t::number_integer:
|
return BasicJsonType(m_view.template get<typename BasicJsonType::number_integer_t>());
|
||||||
return BasicJsonType(m_view.template get<typename BasicJsonType::number_integer_t>());
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
return BasicJsonType(m_view.template get<typename BasicJsonType::number_unsigned_t>());
|
|
||||||
case value_t::number_float:
|
|
||||||
return BasicJsonType(m_view.template get<typename BasicJsonType::number_float_t>());
|
|
||||||
case value_t::boolean:
|
|
||||||
return BasicJsonType(m_view.template get<bool>());
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::object:
|
|
||||||
case value_t::array:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
return BasicJsonType(nullptr);
|
|
||||||
}
|
}
|
||||||
|
if (t == value_t::number_unsigned)
|
||||||
|
{
|
||||||
|
return BasicJsonType(m_view.template get<typename BasicJsonType::number_unsigned_t>());
|
||||||
|
}
|
||||||
|
return BasicJsonType(m_view.template get<typename BasicJsonType::number_float_t>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// start at the first element of this array
|
/// start at the first element of this array
|
||||||
@@ -3683,9 +3675,9 @@ class json_side
|
|||||||
return m_json->size();
|
return m_json->size();
|
||||||
}
|
}
|
||||||
|
|
||||||
string_view_t string() const
|
string_view_t string() const noexcept
|
||||||
{
|
{
|
||||||
const auto& s = m_json->template get_ref<const typename BasicJsonType::string_t&>();
|
const auto& s = *m_json->template get_ptr<const typename BasicJsonType::string_t*>();
|
||||||
return string_view_t(s.data(), s.size());
|
return string_view_t(s.data(), s.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4478,14 +4470,9 @@ struct position_source
|
|||||||
return token{end - start};
|
return token{end - start};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the range [first, last), or none if either is npos
|
/// the range [first, last)
|
||||||
void set(std::size_t first, std::size_t last) noexcept
|
void set(std::size_t first, std::size_t last) noexcept
|
||||||
{
|
{
|
||||||
if (first == std::string::npos || last == std::string::npos)
|
|
||||||
{
|
|
||||||
clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
start = first;
|
start = first;
|
||||||
end = last;
|
end = last;
|
||||||
}
|
}
|
||||||
@@ -4604,11 +4591,11 @@ struct position_source
|
|||||||
first = n.off;
|
first = n.off;
|
||||||
last = n.off + 4u;
|
last = n.off + 4u;
|
||||||
break;
|
break;
|
||||||
case node_kind::object:
|
case node_kind::object: // LCOV_EXCL_LINE
|
||||||
case node_kind::array:
|
case node_kind::array:
|
||||||
case node_kind::link:
|
case node_kind::link:
|
||||||
default:
|
default:
|
||||||
break;
|
break; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
// (a crafted image can point past the text)
|
// (a crafted image can point past the text)
|
||||||
if (last == std::string::npos || last > d.size)
|
if (last == std::string::npos || last > d.size)
|
||||||
@@ -5027,10 +5014,6 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
|
|||||||
end = p.end;
|
end = p.end;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// the open brackets: n, then the last element as long as it is a non-empty container
|
// the open brackets: n, then the last element as long as it is a non-empty container
|
||||||
std::size_t closing = 0;
|
std::size_t closing = 0;
|
||||||
std::size_t pos = std::string::npos;
|
std::size_t pos = std::string::npos;
|
||||||
@@ -5262,10 +5245,12 @@ struct throwing_sax
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LCOV_EXCL_START (throw_parse_failure parses JSON only)
|
||||||
bool binary(binary_t& /*unused*/)
|
bool binary(binary_t& /*unused*/)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
bool start_object(std::size_t /*unused*/)
|
bool start_object(std::size_t /*unused*/)
|
||||||
{
|
{
|
||||||
@@ -5488,7 +5473,7 @@ inline void set_moved(document_data& d, node* n, node* seq, std::size_t cap)
|
|||||||
e.moved_cap[n->off] = cap;
|
e.moved_cap[n->off] = cap;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
reserve_moved(d);
|
// every caller has called reserve_moved() (see above), so neither push_back() can throw
|
||||||
e.moved.push_back(seq);
|
e.moved.push_back(seq);
|
||||||
e.moved_cap.push_back(cap);
|
e.moved_cap.push_back(cap);
|
||||||
n->off = static_cast<std::uint32_t>(e.moved.size() - 1);
|
n->off = static_cast<std::uint32_t>(e.moved.size() - 1);
|
||||||
@@ -5573,7 +5558,7 @@ inline node* find_parent(const document_data& d, const node* target)
|
|||||||
}
|
}
|
||||||
if (target == lo)
|
if (target == lo)
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr; // LCOV_EXCL_LINE (the root, or a new value's root, which is linked)
|
||||||
}
|
}
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
@@ -6194,11 +6179,11 @@ NLOHMANN_VIEW_NOINLINE inline void check_utf8_scan(const unsigned char* p, std::
|
|||||||
}
|
}
|
||||||
if (*q >= 0x80)
|
if (*q >= 0x80)
|
||||||
{
|
{
|
||||||
check_utf8_bytewise(p, n, static_cast<std::size_t>(q - p));
|
break; // ill-formed UTF-8: reported below
|
||||||
return; // not reached: the scan stopped at ill-formed UTF-8
|
|
||||||
}
|
}
|
||||||
++q; // a quote, backslash, or control character
|
++q; // a quote, backslash, or control character
|
||||||
}
|
}
|
||||||
|
check_utf8_bytewise(p, n, static_cast<std::size_t>(q - p));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void check_utf8(const char* s, std::size_t n)
|
inline void check_utf8(const char* s, std::size_t n)
|
||||||
@@ -7792,10 +7777,6 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
|
|||||||
d.edits.reset();
|
d.edits.reset();
|
||||||
d.base[2] = nullptr;
|
d.base[2] = nullptr;
|
||||||
d.owned.clear();
|
d.owned.clear();
|
||||||
if (d.owned_image.empty() || image != d.owned_image.data())
|
|
||||||
{
|
|
||||||
d.owned_image.clear();
|
|
||||||
}
|
|
||||||
d.arena.clear();
|
d.arena.clear();
|
||||||
d.indexes.clear();
|
d.indexes.clear();
|
||||||
d.index_slots.clear();
|
d.index_slots.clear();
|
||||||
@@ -9105,7 +9086,7 @@ class view_serializer
|
|||||||
{
|
{
|
||||||
invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8)
|
invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8)
|
||||||
}
|
}
|
||||||
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
|
write_escaped(s[i]); // a quote, a backslash, or a control character
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
m_out.put('"');
|
m_out.put('"');
|
||||||
@@ -9853,20 +9834,10 @@ class view_serializer
|
|||||||
/// a float node as dump() writes it
|
/// a float node as dump() writes it
|
||||||
void write_float_node(const node& n)
|
void write_float_node(const node& n)
|
||||||
{
|
{
|
||||||
write_float_node(n, fast_double {});
|
// (only for types other than double: doubles go through write_double_ahead())
|
||||||
}
|
|
||||||
|
|
||||||
void write_float_node(const node& n, std::false_type /*other*/)
|
|
||||||
{
|
|
||||||
write_float(float_value<number_float_t>(m_doc, n));
|
write_float(float_value<number_float_t>(m_doc, n));
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_float_node(const node& n, std::true_type /*double*/)
|
|
||||||
{
|
|
||||||
m_out.reserve(64);
|
|
||||||
m_out.set_cursor(write_double_at(m_out.cursor(), n));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief (doubles) the float at n as dump() writes it, at w (64 bytes of room)
|
@brief (doubles) the float at n as dump() writes it, at w (64 bytes of room)
|
||||||
|
|
||||||
@@ -9961,10 +9932,11 @@ class view_serializer
|
|||||||
{
|
{
|
||||||
const string_t dumped = BasicJsonType(string_t(reinterpret_cast<const char*>(s), n)).dump(); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
const string_t dumped = BasicJsonType(string_t(reinterpret_cast<const char*>(s), n)).dump(); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
static_cast<void>(dumped);
|
static_cast<void>(dumped);
|
||||||
}
|
} // LCOV_EXCL_LINE (dump() throws: the callers pass only ill-formed UTF-8)
|
||||||
|
|
||||||
template<bool EnsureAscii>
|
/// write the escape sequence of a quote, a backslash or a control
|
||||||
void write_codepoint(std::uint32_t codepoint, const unsigned char* bytes, std::size_t len)
|
/// character (the only bytes a run of plain output stops at)
|
||||||
|
void write_escaped(std::uint32_t codepoint)
|
||||||
{
|
{
|
||||||
switch (codepoint)
|
switch (codepoint)
|
||||||
{
|
{
|
||||||
@@ -9992,20 +9964,7 @@ class view_serializer
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (codepoint <= 0x1F || (EnsureAscii && codepoint >= 0x7F))
|
write_u_escape(codepoint); // (a control character)
|
||||||
{
|
|
||||||
if (codepoint <= 0xFFFF)
|
|
||||||
{
|
|
||||||
write_u_escape(codepoint);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_u_escape(0xD7C0u + (codepoint >> 10u));
|
|
||||||
write_u_escape(0xDC00u + (codepoint & 0x3FFu));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_out.put(reinterpret_cast<const char*>(bytes), len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) LCOV_EXCL_LINE (printable characters are copied in runs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_u_escape(std::uint32_t u)
|
void write_u_escape(std::uint32_t u)
|
||||||
@@ -11799,9 +11758,11 @@ class basic_json_document
|
|||||||
{
|
{
|
||||||
// (as before: the library's adapter decides what a null stream yields)
|
// (as before: the library's adapter decides what a null stream yields)
|
||||||
detail::view::append_adapter(buf, detail::input_adapter(file));
|
detail::view::append_adapter(buf, detail::input_adapter(file));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
detail::view::append_file(buf, file);
|
else
|
||||||
|
{
|
||||||
|
detail::view::append_file(buf, file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ using nlohmann::json;
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -767,3 +769,69 @@ TEST_CASE("a write that throws leaves the same bytes in every kind of output")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
// an adapter whose every write fails
|
||||||
|
class failing_adapter : public nlohmann::detail::output_adapter_protocol<char>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
[[noreturn]] void write_character(char /*c*/) override
|
||||||
|
{
|
||||||
|
throw std::runtime_error("write failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void write_characters(const char* /*s*/, std::size_t /*length*/) override
|
||||||
|
{
|
||||||
|
throw std::runtime_error("write failed");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST_CASE("output adapters")
|
||||||
|
{
|
||||||
|
SECTION("writing a single character")
|
||||||
|
{
|
||||||
|
// the writers pass whole buffers on, but an adapter takes single
|
||||||
|
// characters as well
|
||||||
|
std::vector<char> as_vector;
|
||||||
|
nlohmann::detail::output_vector_adapter<char> vector_adapter(as_vector);
|
||||||
|
vector_adapter.write_character('a');
|
||||||
|
vector_adapter.write_character('b');
|
||||||
|
CHECK(as_vector == std::vector<char>({'a', 'b'}));
|
||||||
|
|
||||||
|
std::ostringstream as_stream;
|
||||||
|
nlohmann::detail::output_stream_adapter<char> stream_adapter(as_stream);
|
||||||
|
stream_adapter.write_character('c');
|
||||||
|
stream_adapter.write_character('d');
|
||||||
|
CHECK(as_stream.str() == "cd");
|
||||||
|
|
||||||
|
std::string as_string;
|
||||||
|
nlohmann::detail::output_string_adapter<char> string_adapter(as_string);
|
||||||
|
string_adapter.write_character('e');
|
||||||
|
string_adapter.write_character('f');
|
||||||
|
CHECK(as_string == "ef");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
SECTION("a sink that is destroyed with characters left over")
|
||||||
|
{
|
||||||
|
// the destructor passes the pending characters on, as a write that
|
||||||
|
// threw leaves them there; the adapter failing then must not escape
|
||||||
|
{
|
||||||
|
nlohmann::detail::output_adapter_sink<char> sink(std::make_shared<failing_adapter>());
|
||||||
|
sink.write_character('x');
|
||||||
|
}
|
||||||
|
|
||||||
|
// while flush() reports the failure
|
||||||
|
nlohmann::detail::output_adapter_sink<char> sink(std::make_shared<failing_adapter>());
|
||||||
|
sink.write_character('x');
|
||||||
|
CHECK_THROWS_AS(sink.flush(), std::runtime_error&);
|
||||||
|
// (the character counts as written, so there is nothing left for the destructor)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -78,25 +78,23 @@ TEST_CASE("BJData")
|
|||||||
{
|
{
|
||||||
SECTION("binary_reader BJData lookup tables")
|
SECTION("binary_reader BJData lookup tables")
|
||||||
{
|
{
|
||||||
// bjd_type_name() is static; is_valid_optimized_type() needs a BJData reader
|
// both lookups are static member functions
|
||||||
std::vector<std::uint8_t> const data;
|
std::vector<std::uint8_t> const data;
|
||||||
using reader_t = nlohmann::detail::binary_reader<json, decltype(nlohmann::detail::input_adapter(data))>;
|
using reader_t = nlohmann::detail::binary_reader<json, decltype(nlohmann::detail::input_adapter(data))>;
|
||||||
|
|
||||||
// the markers BJData excludes as optimized types must match
|
// the excluded optimized-type markers must match binary_writer's
|
||||||
// binary_writer's is_bjdata_excluded_type_marker(), which encodes the
|
// is_bjdata_excluded_type_marker(), which encodes the same 8 markers
|
||||||
// same 8 markers
|
|
||||||
reader_t reader(nlohmann::detail::input_adapter(data), json::input_format_t::bjdata);
|
|
||||||
for (const char marker :
|
for (const char marker :
|
||||||
{'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z', 'x'
|
{'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CHECK(!reader.is_valid_optimized_type(static_cast<unsigned char>(marker)));
|
CHECK(reader_t::is_bjd_excluded_optimized_type(static_cast<unsigned char>(marker)));
|
||||||
}
|
}
|
||||||
for (const char marker :
|
for (const char marker :
|
||||||
{'U', 'i', 'u', 'I', 'm', 'l', 'M', 'L', 'd', 'D', 'C', 'B', 'h'
|
{'U', 'i', 'u', 'I', 'm', 'l', 'M', 'L', 'd', 'D', 'C', 'B', 'x'
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CHECK(reader.is_valid_optimized_type(static_cast<unsigned char>(marker)));
|
CHECK(!reader_t::is_bjd_excluded_optimized_type(static_cast<unsigned char>(marker)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// every dtype marker must round-trip to its ND-array type name
|
// every dtype marker must round-trip to its ND-array type name
|
||||||
@@ -2314,6 +2312,23 @@ TEST_CASE("BJData")
|
|||||||
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("every event of an ndarray of chars")
|
||||||
|
{
|
||||||
|
// the elements of an ndarray of 'C' are read one by one
|
||||||
|
std::vector<uint8_t> const v = {'[', '$', 'C', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 'a', 'b', 'c', 'd'};
|
||||||
|
// start_object, key, string, key, start_array, 2 numbers, end_array,
|
||||||
|
// key, start_array, 4 chars, end_array, end_object
|
||||||
|
constexpr int events = 16;
|
||||||
|
for (int allowed = 0; allowed < events; ++allowed)
|
||||||
|
{
|
||||||
|
CAPTURE(allowed)
|
||||||
|
SaxCountdown scp(allowed);
|
||||||
|
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||||
|
}
|
||||||
|
SaxCountdown scp(events);
|
||||||
|
CHECK(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("start_array() in ndarray _ArrayData_")
|
SECTION("start_array() in ndarray _ArrayData_")
|
||||||
{
|
{
|
||||||
std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', 'i', 2, 'i', 3, ']', 6, 5, 4, 3, 2, 1};
|
std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', 'i', 2, 'i', 3, ']', 6, 5, 4, 3, 2, 1};
|
||||||
@@ -3097,6 +3112,15 @@ TEST_CASE("BJData")
|
|||||||
json _;
|
json _;
|
||||||
CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82");
|
CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("ndarray of chars cut off by the end of the input")
|
||||||
|
{
|
||||||
|
// 2x2 chars announced, three present
|
||||||
|
std::vector<uint8_t> const v = {'[', '$', 'C', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 'a', 'b', 'c'};
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 16: syntax error while parsing BJData char: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK(json::from_bjdata(v, true, false).is_discarded());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("byte")
|
SECTION("byte")
|
||||||
|
|||||||
@@ -329,6 +329,9 @@ TEST_CASE("BON8")
|
|||||||
check_bon8({false, nullptr, true}, {0x83, 0xF8, 0xFA, 0xF9});
|
check_bon8({false, nullptr, true}, {0x83, 0xF8, 0xFA, 0xF9});
|
||||||
check_bon8({false, nullptr, true, 1.0}, {0x84, 0xF8, 0xFA, 0xF9, 0xFD});
|
check_bon8({false, nullptr, true, 1.0}, {0x84, 0xF8, 0xFA, 0xF9, 0xFD});
|
||||||
check_bon8({false, nullptr, true, 1.0, json::array(), 0.0}, {0x85, 0xF8, 0xFA, 0xF9, 0xFD, 0x80, 0xFC, 0xFE});
|
check_bon8({false, nullptr, true, 1.0, json::array(), 0.0}, {0x85, 0xF8, 0xFA, 0xF9, 0xFD, 0x80, 0xFC, 0xFE});
|
||||||
|
// an array that starts with a number: -1, 0, and 1 have bytes of their own
|
||||||
|
check_bon8({-1.0, 0.0, 1.0}, {0x83, 0xFB, 0xFC, 0xFD});
|
||||||
|
check_bon8({1.0, -1.0, -0.0}, {0x83, 0xFD, 0xFB, 0x8E, 0x80, 0x00, 0x00, 0x00});
|
||||||
check_bon8({{{1}}}, {0x81, 0x81, 0x81, 0x91});
|
check_bon8({{{1}}}, {0x81, 0x81, 0x81, 0x91});
|
||||||
check_bon8({{{"foo"}}}, {0x81, 0x81, 0x81, 'f', 'o', 'o', 0xFF});
|
check_bon8({{{"foo"}}}, {0x81, 0x81, 0x81, 'f', 'o', 'o', 0xFF});
|
||||||
check_bon8({{{""}}}, {0x81, 0x81, 0x81, 0xFF});
|
check_bon8({{{""}}}, {0x81, 0x81, 0x81, 0xFF});
|
||||||
|
|||||||
+75
-38
@@ -23,7 +23,6 @@ using nlohmann::json;
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <list>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
@@ -82,8 +81,20 @@ class huge_string_t : public std::string
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns a copy of @a s whose size() pretends to be @a n
|
||||||
|
static huge_string_t as_size(const std::string& s, const size_type n)
|
||||||
|
{
|
||||||
|
huge_string_t result(s);
|
||||||
|
result.fake_size = n;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
size_type size() const noexcept
|
size_type size() const noexcept
|
||||||
{
|
{
|
||||||
|
if (fake_size != 0)
|
||||||
|
{
|
||||||
|
return fake_size;
|
||||||
|
}
|
||||||
if (pretend_huge)
|
if (pretend_huge)
|
||||||
{
|
{
|
||||||
// one byte more than the BSON length field can represent
|
// one byte more than the BSON length field can represent
|
||||||
@@ -94,6 +105,7 @@ class huge_string_t : public std::string
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
bool pretend_huge = false;
|
bool pretend_huge = false;
|
||||||
|
size_type fake_size = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
using huge_string_json = nlohmann::json::with_string_t<huge_string_t>;
|
using huge_string_json = nlohmann::json::with_string_t<huge_string_t>;
|
||||||
@@ -268,6 +280,17 @@ TEST_CASE("BSON")
|
|||||||
CHECK_THROWS_WITH_AS(huge_string_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483661 exceeds maximum of 2147483647", huge_string_json::out_of_range&);
|
CHECK_THROWS_WITH_AS(huge_string_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483661 exceeds maximum of 2147483647", huge_string_json::out_of_range&);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("an array element that no longer fits the document")
|
||||||
|
{
|
||||||
|
// the string fits the length field, but not the document that
|
||||||
|
// holds it; nothing of it is written
|
||||||
|
const auto limit = static_cast<std::string::size_type>((std::numeric_limits<std::int32_t>::max)());
|
||||||
|
huge_string_json j;
|
||||||
|
j["a"] = huge_string_json::array({huge_string_t::as_size("value", limit - 8)});
|
||||||
|
|
||||||
|
CHECK_THROWS_AS(huge_string_json::to_bson(j), huge_string_json::out_of_range&);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("document")
|
SECTION("document")
|
||||||
{
|
{
|
||||||
// an oversized string nested one level deep makes the
|
// an oversized string nested one level deep makes the
|
||||||
@@ -899,6 +922,38 @@ TEST_CASE("BSON")
|
|||||||
CHECK_THROWS_AS(_ = json::from_bson(input), json::parse_error&);
|
CHECK_THROWS_AS(_ = json::from_bson(input), json::parse_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("binary member with subtype 0x02 (old binary) with a truncated inner length")
|
||||||
|
{
|
||||||
|
// the inner length field is cut off by the end of the input
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x0F, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x05, 'a', 0x00, // entry: binary
|
||||||
|
0x08, 0x00, 0x00, 0x00, // size of binary (little endian)
|
||||||
|
0x02, // "old binary" subtype
|
||||||
|
0x01 // first byte of the inner length
|
||||||
|
};
|
||||||
|
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.110] parse error at byte 14: syntax error while parsing BSON number: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("string member that is cut off by the end of the input")
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x0F, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x02, 'a', 0x00, // entry: string
|
||||||
|
0x0A, 0x00, 0x00, 0x00, // size of the string (little endian)
|
||||||
|
'b', 'c' // the string ends early
|
||||||
|
};
|
||||||
|
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.110] parse error at byte 14: syntax error while parsing BSON string: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("Some more complex document")
|
SECTION("Some more complex document")
|
||||||
{
|
{
|
||||||
json const j =
|
json const j =
|
||||||
@@ -1200,31 +1255,6 @@ TEST_CASE("BSON keys from contiguous and stream input")
|
|||||||
from_stream = e.what();
|
from_stream = e.what();
|
||||||
}
|
}
|
||||||
CHECK(from_vector == from_stream);
|
CHECK(from_vector == from_stream);
|
||||||
|
|
||||||
// error_handler_t::strict checks the array element names in place on
|
|
||||||
// contiguous input and byte by byte otherwise; the names are
|
|
||||||
// well-formed, so both give the same value or error as above
|
|
||||||
std::string strict_from_vector;
|
|
||||||
std::string strict_from_list;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
strict_from_vector = json::from_bson(input, true, true, json::error_handler_t::strict).dump();
|
|
||||||
}
|
|
||||||
catch (const json::parse_error& e)
|
|
||||||
{
|
|
||||||
strict_from_vector = e.what();
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
const std::list<std::uint8_t> list(input.begin(), input.end());
|
|
||||||
strict_from_list = json::from_bson(list.begin(), list.end(), true, true, json::error_handler_t::strict).dump();
|
|
||||||
}
|
|
||||||
catch (const json::parse_error& e)
|
|
||||||
{
|
|
||||||
strict_from_list = e.what();
|
|
||||||
}
|
|
||||||
CHECK(strict_from_vector == from_vector);
|
|
||||||
CHECK(strict_from_list == from_vector);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1253,18 +1283,6 @@ TEST_CASE("BSON array element names")
|
|||||||
|
|
||||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(bson.data()), bson.size()));
|
std::istringstream stream(std::string(reinterpret_cast<const char*>(bson.data()), bson.size()));
|
||||||
CHECK(json::from_bson(stream) == expected);
|
CHECK(json::from_bson(stream) == expected);
|
||||||
|
|
||||||
// input that is not contiguous reads the names byte by byte and must reject
|
|
||||||
// the same name
|
|
||||||
const std::list<std::uint8_t> list(bson.begin(), bson.end());
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(list.begin(), list.end(), true, true, json::error_handler_t::strict), "[json.exception.parse_error.113] parse error at byte 14: syntax error while parsing BSON key: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
|
||||||
|
|
||||||
// without exceptions, the rejected name ends the parse
|
|
||||||
CHECK(json::from_bson(bson, true, false, json::error_handler_t::strict).is_discarded());
|
|
||||||
|
|
||||||
// well-formed names pass the check without being stored
|
|
||||||
const json valid = {{"a", {true, false, {{"k", json::array({1, 2})}}}}};
|
|
||||||
CHECK(json::from_bson(json::to_bson(valid), true, true, json::error_handler_t::strict) == valid);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -2174,6 +2192,25 @@ value = depth % 2 == 0 ? json{{"a", std::move(value)}, {"b", {1, "x"}}} :
|
|||||||
CHECK(output.empty());
|
CHECK(output.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
SECTION("U+0000 in a long key is found at any position")
|
||||||
|
{
|
||||||
|
// the key is searched eight characters at a time
|
||||||
|
for (const std::string& key :
|
||||||
|
{
|
||||||
|
std::string("abc\0defghijkl", 12), std::string("abcdefgh\0ijkl", 13), std::string("abcdefghijklmnop\0", 17), std::string("\0abcdefghijkl", 13),
|
||||||
|
std::string("abcdefghijklmno\0", 16)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(key)
|
||||||
|
const json value = json::object({{key, 1}});
|
||||||
|
std::vector<std::uint8_t> output;
|
||||||
|
CHECK_THROWS_AS(json::to_bson(value, output), json::out_of_range&);
|
||||||
|
CHECK(output.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
SECTION("a binary subtype that doesn't fit a byte is rejected before anything is written (#5675)")
|
SECTION("a binary subtype that doesn't fit a byte is rejected before anything is written (#5675)")
|
||||||
{
|
{
|
||||||
// the offending value is nested, so this also covers that the check
|
// the offending value is nested, so this also covers that the check
|
||||||
|
|||||||
@@ -1809,6 +1809,30 @@ TEST_CASE("CBOR")
|
|||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("tag in front of a key that is cut off by the end of the input")
|
||||||
|
{
|
||||||
|
// 0xD8: the tag number is in the next byte, which is missing
|
||||||
|
const std::vector<std::uint8_t> input = {0xA1, 0xD8};
|
||||||
|
json _;
|
||||||
|
for (const auto handler :
|
||||||
|
{
|
||||||
|
json::cbor_tag_handler_t::ignore, json::cbor_tag_handler_t::store
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input, true, true, handler), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK(json::from_cbor(input, true, false, handler).is_discarded());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("chunk of an indefinite-length string that is cut off by the end of the input")
|
||||||
|
{
|
||||||
|
// the second byte announces a chunk of three bytes, but two follow
|
||||||
|
const std::vector<std::uint8_t> input = {0x7F, 0x63, 'a', 'b'};
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK(json::from_cbor(input, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("tag in front of a key")
|
SECTION("tag in front of a key")
|
||||||
{
|
{
|
||||||
const auto ignore = json::cbor_tag_handler_t::ignore;
|
const auto ignore = json::cbor_tag_handler_t::ignore;
|
||||||
|
|||||||
@@ -2092,6 +2092,12 @@ TEST_CASE("UTF-8 validators accept the same sequences")
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// an ASCII byte is a sequence of its own
|
||||||
|
for (unsigned lead = 0; lead < 0x80; ++lead)
|
||||||
|
{
|
||||||
|
CHECK(nlohmann::detail::utf8_shape(static_cast<std::uint8_t>(lead)).length == 1);
|
||||||
|
}
|
||||||
|
|
||||||
for (unsigned lead = 0x80; lead <= 0xFF; ++lead)
|
for (unsigned lead = 0x80; lead <= 0xFF; ++lead)
|
||||||
{
|
{
|
||||||
const auto shape = nlohmann::detail::utf8_shape(static_cast<std::uint8_t>(lead));
|
const auto shape = nlohmann::detail::utf8_shape(static_cast<std::uint8_t>(lead));
|
||||||
@@ -2157,3 +2163,72 @@ TEST_CASE("UTF-8 validators accept the same sequences")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("float_bigint")
|
||||||
|
{
|
||||||
|
// the big integer of the exact comparison of a float token with the
|
||||||
|
// midpoint between two floats (digit_comparison())
|
||||||
|
using nlohmann::detail::float_bigint;
|
||||||
|
|
||||||
|
SECTION("zero")
|
||||||
|
{
|
||||||
|
// shifting zero keeps it zero, whatever the distance
|
||||||
|
float_bigint zero(0);
|
||||||
|
zero.shift_left(0);
|
||||||
|
zero.shift_left(5);
|
||||||
|
zero.shift_left(64);
|
||||||
|
CHECK(zero.compare(float_bigint(0)) == 0);
|
||||||
|
CHECK(zero.compare(float_bigint(1)) == -1);
|
||||||
|
CHECK(float_bigint(1).compare(zero) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("compare numbers of different lengths")
|
||||||
|
{
|
||||||
|
// the number with more 32-bit limbs is the greater one
|
||||||
|
const float_bigint small(0xFFFFFFFFu);
|
||||||
|
const float_bigint large(std::uint64_t{1} << 32u);
|
||||||
|
CHECK(small.compare(large) == -1);
|
||||||
|
CHECK(large.compare(small) == 1);
|
||||||
|
CHECK(large.compare(large) == 0);
|
||||||
|
|
||||||
|
// a shift can add a limb
|
||||||
|
float_bigint shifted(1);
|
||||||
|
shifted.shift_left(32);
|
||||||
|
CHECK(shifted.compare(large) == 0);
|
||||||
|
shifted.shift_left(1);
|
||||||
|
CHECK(shifted.compare(large) == 1);
|
||||||
|
CHECK(large.compare(shifted) == -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("compare numbers of the same length")
|
||||||
|
{
|
||||||
|
CHECK(float_bigint(2).compare(float_bigint(3)) == -1);
|
||||||
|
CHECK(float_bigint(3).compare(float_bigint(2)) == 1);
|
||||||
|
CHECK(float_bigint(3).compare(float_bigint(3)) == 0);
|
||||||
|
CHECK(float_bigint((std::uint64_t{3} << 32u) | 1u).compare(float_bigint((std::uint64_t{3} << 32u) | 2u)) == -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("serialization of short strings")
|
||||||
|
{
|
||||||
|
// the serializer takes strings of up to 32 characters that need no
|
||||||
|
// escaping by a table lookup of its own where the scan is not vectorized
|
||||||
|
// (this file is built that way, too, with JSON_NO_SIMD)
|
||||||
|
for (const std::string& s :
|
||||||
|
{
|
||||||
|
std::string(), std::string("a"), std::string("hello"), std::string("0123456789abcdef"), std::string("0123456789abcdef0123456789abcdef"),
|
||||||
|
std::string("0123456789abcdef0123456789abcdef0")
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(s)
|
||||||
|
const json j = s;
|
||||||
|
CHECK(j.dump() == "\"" + s + "\"");
|
||||||
|
CHECK(j.dump(-1, ' ', true) == "\"" + s + "\"");
|
||||||
|
CHECK(json({{s, s}}).dump() == "{\"" + s + "\":\"" + s + "\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// a character that needs an escape
|
||||||
|
CHECK(json("a\"b").dump() == "\"a\\\"b\"");
|
||||||
|
CHECK(json("caf\xC3\xA9").dump() == "\"caf\xC3\xA9\"");
|
||||||
|
CHECK(json("caf\xC3\xA9").dump(-1, ' ', true) == "\"caf\\u00e9\"");
|
||||||
|
}
|
||||||
@@ -210,3 +210,23 @@ TEST_CASE("regression test #5476 - array type without reserve()")
|
|||||||
CHECK(json::from_cbor(truncated, true, false).is_discarded());
|
CHECK(json::from_cbor(truncated, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("array type with std::deque nested beyond the recursion limit")
|
||||||
|
{
|
||||||
|
// the binary writers finish a value nested deeper than
|
||||||
|
// recursion_depth_limit() with an explicit stack of frames; with the
|
||||||
|
// iterators of std::deque, these frames are copied and moved through
|
||||||
|
// their own special members
|
||||||
|
deque_json j = 1;
|
||||||
|
for (int i = 0; i < 200; ++i)
|
||||||
|
{
|
||||||
|
deque_json outer = deque_json::array();
|
||||||
|
outer.push_back(std::move(j));
|
||||||
|
j = std::move(outer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 200 arrays of one element each (0x81) around the integer 1
|
||||||
|
std::vector<std::uint8_t> expected(200, 0x81);
|
||||||
|
expected.push_back(0x01);
|
||||||
|
CHECK(deque_json::to_cbor(j) == expected);
|
||||||
|
}
|
||||||
@@ -49,3 +49,24 @@ TEST_CASE("custom object key types: MessagePack (json_full)")
|
|||||||
{
|
{
|
||||||
custom_key_test::test_msgpack<json_full>();
|
custom_key_test::test_msgpack<json_full>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("custom object key types: patch (json_full)")
|
||||||
|
{
|
||||||
|
// the members of a patch are found by lookup, as their keys are not std::string
|
||||||
|
const json_full j = json_full::parse(R"({"a":1,"b":[1,2],"c":{"d":2}})");
|
||||||
|
const json_full patch = json_full::parse(R"([
|
||||||
|
{"op": "add", "path": "/e", "value": 5},
|
||||||
|
{"op": "remove", "path": "/a"},
|
||||||
|
{"op": "replace", "path": "/c/d", "value": 3},
|
||||||
|
{"op": "test", "path": "/c/d", "value": 3},
|
||||||
|
{"op": "move", "from": "/e", "path": "/f"},
|
||||||
|
{"op": "copy", "from": "/f", "path": "/g"},
|
||||||
|
{"op": "add", "path": "/b/-", "value": 7}
|
||||||
|
])");
|
||||||
|
|
||||||
|
CHECK(j.patch(patch).dump() == R"({"b":[1,2,7],"c":{"d":3},"f":5,"g":5})");
|
||||||
|
|
||||||
|
// a patch with a missing member is rejected
|
||||||
|
CHECK_THROWS_AS(j.patch(json_full::parse(R"([{"op": "add", "path": "/e"}])")), json_full::parse_error&);
|
||||||
|
CHECK_THROWS_AS(j.patch(json_full::parse(R"([{"op": "test", "path": "/a", "value": 2}])")), json_full::other_error&);
|
||||||
|
}
|
||||||
@@ -337,6 +337,52 @@ TEST_CASE("custom number types: integers beyond 64 bits")
|
|||||||
CHECK(wide_json::to_bson(fit_members) == nlohmann::json::to_bson(fit_members64));
|
CHECK(wide_json::to_bson(fit_members) == nlohmann::json::to_bson(fit_members64));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("MessagePack integers of every width")
|
||||||
|
{
|
||||||
|
// an integer of a number type wider than 64 bits is written with the
|
||||||
|
// smallest MessagePack integer that holds it, as for 64-bit types
|
||||||
|
const wide_json widths = wide_json::array(
|
||||||
|
{
|
||||||
|
make_int(-33), make_int(-100), make_int(-128), make_int(-129), make_int(-32768), make_int(-32769), make_int(-2147483648LL), make_int(-2147483649LL),
|
||||||
|
make_int(128), make_int(255), make_int(256), make_int(300), make_int(65535), make_int(65536), make_int(70000), make_int(4294967295LL), make_int(4294967296LL),
|
||||||
|
make_uint(128), make_uint(255), make_uint(256), make_uint(300), make_uint(65535), make_uint(65536), make_uint(70000), make_uint(4294967295ULL), make_uint(4294967296ULL)
|
||||||
|
});
|
||||||
|
const nlohmann::json widths64 = nlohmann::json::array(
|
||||||
|
{
|
||||||
|
-33, -100, -128, -129, -32768, -32769, -2147483648LL, -2147483649LL,
|
||||||
|
128, 255, 256, 300, 65535, 65536, 70000, 4294967295LL, 4294967296LL,
|
||||||
|
128u, 255u, 256u, 300u, 65535u, 65536u, 70000u, 4294967295ULL, 4294967296ULL
|
||||||
|
});
|
||||||
|
CHECK(wide_json::to_msgpack(widths) == nlohmann::json::to_msgpack(widths64));
|
||||||
|
|
||||||
|
// as single values, too
|
||||||
|
CHECK(wide_json::to_msgpack(make_int(-100)) == std::vector<std::uint8_t>({0xD0, 0x9C}));
|
||||||
|
CHECK(wide_json::to_msgpack(make_int(300)) == std::vector<std::uint8_t>({0xCD, 0x01, 0x2C}));
|
||||||
|
CHECK(wide_json::to_msgpack(make_int(70000)) == std::vector<std::uint8_t>({0xCE, 0x00, 0x01, 0x11, 0x70}));
|
||||||
|
CHECK(wide_json::to_msgpack(make_uint(300)) == std::vector<std::uint8_t>({0xCD, 0x01, 0x2C}));
|
||||||
|
CHECK(wide_json::to_msgpack(make_uint(70000)) == std::vector<std::uint8_t>({0xCE, 0x00, 0x01, 0x11, 0x70}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("dump")
|
||||||
|
{
|
||||||
|
// beyond 64 bits, the digits are counted before they are written
|
||||||
|
CHECK(make_int(0).dump() == "0");
|
||||||
|
CHECK(make_int(two_64).dump() == "18446744073709551616");
|
||||||
|
CHECK(make_int(-two_64).dump() == "-18446744073709551616");
|
||||||
|
CHECK(make_int(two_100).dump() == "1267650600228229401496703205376");
|
||||||
|
CHECK(make_int(-two_100).dump() == "-1267650600228229401496703205376");
|
||||||
|
// powers of ten, where the count of digits ends on a single digit or two
|
||||||
|
const int128 ten_20 = static_cast<int128>(100000000000ULL) * static_cast<int128>(1000000000ULL);
|
||||||
|
CHECK(make_int(ten_20).dump() == "100000000000000000000");
|
||||||
|
CHECK(make_int(-ten_20).dump() == "-100000000000000000000");
|
||||||
|
CHECK(make_int(ten_20 * 10).dump() == "1000000000000000000000");
|
||||||
|
CHECK(make_int(ten_20 * 100).dump() == "10000000000000000000000");
|
||||||
|
CHECK(make_int(ten_20 * 1000).dump() == "100000000000000000000000");
|
||||||
|
CHECK(make_uint(static_cast<uint128>(ten_20) * 10u).dump() == "1000000000000000000000");
|
||||||
|
CHECK(make_uint((std::numeric_limits<uint128>::max)()).dump() == "340282366920938463463374607431768211455");
|
||||||
|
CHECK(make_int((std::numeric_limits<int128>::min)()).dump() == "-170141183460469231731687303715884105728");
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("BJData ND-array")
|
SECTION("BJData ND-array")
|
||||||
{
|
{
|
||||||
// an ND-array element beyond 64 bits does not fit any dtype, so the
|
// an ND-array element beyond 64 bits does not fit any dtype, so the
|
||||||
@@ -417,3 +463,23 @@ TEST_CASE("custom number types")
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("custom number types: 32-bit integers in the binary formats")
|
||||||
|
{
|
||||||
|
// integers narrower than 64 bits are widened (a signed one with its sign)
|
||||||
|
// before the CBOR, MessagePack, and UBJSON writers pick their encoding
|
||||||
|
using json32 = nlohmann::json::with_integers_t<std::int32_t, std::uint32_t>;
|
||||||
|
json32 values = json32::array();
|
||||||
|
values.push_back(std::uint32_t{0});
|
||||||
|
values.push_back(std::uint32_t{200});
|
||||||
|
values.push_back(std::uint32_t{4294967295u});
|
||||||
|
values.push_back(std::int32_t{-1});
|
||||||
|
values.push_back(std::int32_t{-2147483647 - 1});
|
||||||
|
REQUIRE(values[2].is_number_unsigned());
|
||||||
|
REQUIRE(values[4].is_number_integer());
|
||||||
|
|
||||||
|
const nlohmann::json values64 = {0u, 200u, 4294967295u, -1, -2147483648LL};
|
||||||
|
CHECK(json32::to_cbor(values) == nlohmann::json::to_cbor(values64));
|
||||||
|
CHECK(json32::to_msgpack(values) == nlohmann::json::to_msgpack(values64));
|
||||||
|
CHECK(json32::to_ubjson(values) == nlohmann::json::to_ubjson(values64));
|
||||||
|
}
|
||||||
@@ -11,8 +11,13 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "custom_object_key_type.hpp"
|
#include "custom_object_key_type.hpp"
|
||||||
|
|
||||||
@@ -125,3 +130,161 @@ TEST_CASE("MessagePack supports custom object key types nested deeper than the r
|
|||||||
CHECK(encoded == nlohmann::json::to_msgpack(expected));
|
CHECK(encoded == nlohmann::json::to_msgpack(expected));
|
||||||
CHECK(nlohmann::json::from_msgpack(encoded) == expected);
|
CHECK(nlohmann::json::from_msgpack(encoded) == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// A key type that cannot be constructed from a std::string, so that a value
|
||||||
|
// with such keys cannot be converted from a nlohmann::json by the range
|
||||||
|
// constructor of its object type: the serializer converts the object instead.
|
||||||
|
class strict_key
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
strict_key(const char* value)
|
||||||
|
: m_value(value)
|
||||||
|
{}
|
||||||
|
|
||||||
|
strict_key(const std::string& /*value*/) = delete;
|
||||||
|
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
// Required by JSON_DIAGNOSTICS, which reads object keys through data()
|
||||||
|
// when building the path of an exception.
|
||||||
|
const char* data() const noexcept
|
||||||
|
{
|
||||||
|
return m_value.data();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
friend bool operator<(const strict_key& lhs, const strict_key& rhs)
|
||||||
|
{
|
||||||
|
return lhs.m_value < rhs.m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_value{};
|
||||||
|
};
|
||||||
|
|
||||||
|
strict_key make_strict_key(const std::string& key)
|
||||||
|
{
|
||||||
|
return strict_key(key.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Key, class Value, class Compare, class Allocator>
|
||||||
|
class strict_object
|
||||||
|
: public std::map <
|
||||||
|
strict_key,
|
||||||
|
Value,
|
||||||
|
std::less<strict_key>, // NOLINT(modernize-use-transparent-functors)
|
||||||
|
typename std::allocator_traits<Allocator>::template rebind_alloc <
|
||||||
|
std::pair<const strict_key, Value >>>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
strict_object() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T, typename = void>
|
||||||
|
struct strict_serializer : nlohmann::adl_serializer<T> {};
|
||||||
|
|
||||||
|
using strict_json = nlohmann::basic_json < strict_object, std::vector, std::string, bool, std::int64_t,
|
||||||
|
std::uint64_t, double, std::allocator, strict_serializer, std::vector<std::uint8_t >>;
|
||||||
|
|
||||||
|
// the members of an object are converted one by one, their keys by make_strict_key()
|
||||||
|
template<>
|
||||||
|
struct strict_serializer<nlohmann::json::object_t, void>
|
||||||
|
{
|
||||||
|
static void to_json(strict_json& j, const nlohmann::json::object_t& object)
|
||||||
|
{
|
||||||
|
strict_json::object_t converted;
|
||||||
|
for (const auto& member : object)
|
||||||
|
{
|
||||||
|
converted.emplace(make_strict_key(member.first), strict_json(member.second));
|
||||||
|
}
|
||||||
|
j = strict_json(std::move(converted));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("converting a value with keys that cannot be made from a std::string nested deeper than the recursion depth limit")
|
||||||
|
{
|
||||||
|
static_assert(!std::is_constructible<strict_key, const std::string&>::value, "the keys cannot be made from a std::string");
|
||||||
|
|
||||||
|
// below detail::recursion_depth_limit(), objects and arrays are converted
|
||||||
|
// by the serializers instead of the range constructors
|
||||||
|
const std::size_t depth = nlohmann::detail::recursion_depth_limit() + 10;
|
||||||
|
|
||||||
|
nlohmann::json value = 1;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
if (i % 2 == 0)
|
||||||
|
{
|
||||||
|
value = nlohmann::json::object({{"a", std::move(value)}, {"b", true}});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = nlohmann::json::array({std::move(value), "sibling"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const strict_json converted = value;
|
||||||
|
|
||||||
|
// at() needs to make a std::string of a key for its error message
|
||||||
|
const auto member = [](const strict_json & object, const char* name) -> const strict_json &
|
||||||
|
{
|
||||||
|
const auto it = object.find(strict_key(name));
|
||||||
|
REQUIRE(it != object.end());
|
||||||
|
return *it;
|
||||||
|
};
|
||||||
|
|
||||||
|
// walk down the nesting levels without recursion
|
||||||
|
const strict_json* current = &converted;
|
||||||
|
for (std::size_t i = depth; i > 0; --i)
|
||||||
|
{
|
||||||
|
if (i % 2 == 1)
|
||||||
|
{
|
||||||
|
REQUIRE(current->is_object());
|
||||||
|
CHECK(current->size() == 2);
|
||||||
|
CHECK(member(*current, "b").get<bool>());
|
||||||
|
current = &member(*current, "a");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
REQUIRE(current->is_array());
|
||||||
|
CHECK(current->size() == 2);
|
||||||
|
CHECK(current->at(1).get<std::string>() == "sibling");
|
||||||
|
current = ¤t->at(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CHECK(current->get<int>() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// shorter keys come first
|
||||||
|
struct shortlex_less
|
||||||
|
{
|
||||||
|
bool operator()(const std::string& lhs, const std::string& rhs) const
|
||||||
|
{
|
||||||
|
return lhs.size() != rhs.size() ? lhs.size() < rhs.size() : lhs < rhs;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Key, class Value, class Compare, class Allocator>
|
||||||
|
using shortlex_map = std::map<Key, Value, shortlex_less, Allocator>;
|
||||||
|
|
||||||
|
using shortlex_json = nlohmann::json::with_object_t<shortlex_map>;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("unflatten with an object type that does not sort the keys alphabetically")
|
||||||
|
{
|
||||||
|
// the keys of /a come before and after one of /b: unlike in an object
|
||||||
|
// sorted alphabetically, the values below a prefix are not found one
|
||||||
|
// after the other, so the value of /a must be looked up again
|
||||||
|
shortlex_json flat = shortlex_json::object();
|
||||||
|
flat["/a/p"] = 1;
|
||||||
|
flat["/b/qq"] = 2;
|
||||||
|
flat["/a/pqr"] = 3;
|
||||||
|
REQUIRE(flat.begin().key() == "/a/p");
|
||||||
|
REQUIRE((++flat.begin()).key() == "/b/qq");
|
||||||
|
|
||||||
|
CHECK(flat.unflatten().dump() == R"({"a":{"p":1,"pqr":3},"b":{"qq":2}})");
|
||||||
|
CHECK(flat.unflatten().flatten() == flat);
|
||||||
|
}
|
||||||
@@ -1195,6 +1195,28 @@ TEST_CASE("deserialization")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||||
|
TEST_CASE("deserialization from a pointer and a length (deprecated)")
|
||||||
|
{
|
||||||
|
// the unit test build disables -Wdeprecated-declarations (see
|
||||||
|
// cmake/clang_flags.cmake)
|
||||||
|
const std::string text = R"({"foo": [1, 2, 3]})";
|
||||||
|
const std::string invalid = R"({"foo": )";
|
||||||
|
|
||||||
|
SECTION("parse")
|
||||||
|
{
|
||||||
|
const json j = json::parse({text.data(), text.size()});
|
||||||
|
CHECK(j == json({{"foo", {1, 2, 3}}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("accept")
|
||||||
|
{
|
||||||
|
CHECK(json::accept({text.data(), text.size()}));
|
||||||
|
CHECK_FALSE(json::accept({invalid.data(), invalid.size()}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// select the types to test - char8_t is only available since C++20 if and only
|
// select the types to test - char8_t is only available since C++20 if and only
|
||||||
// if __cpp_char8_t is defined.
|
// if __cpp_char8_t is defined.
|
||||||
#define TYPE_LIST(...) __VA_ARGS__
|
#define TYPE_LIST(...) __VA_ARGS__
|
||||||
|
|||||||
@@ -868,3 +868,23 @@ TEST_CASE("Parent pointers stay consistent for object types not known to keep th
|
|||||||
obj["1"].push_back(42);
|
obj["1"].push_back(42);
|
||||||
CHECK_THROWS_WITH_AS(obj["1"].at(5), "[json.exception.out_of_range.401] (/1) array index 5 is out of range", relocating_json::out_of_range&);
|
CHECK_THROWS_WITH_AS(obj["1"].at(5), "[json.exception.out_of_range.401] (/1) array index 5 is out of range", relocating_json::out_of_range&);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("patch_inplace() with a patch that is stored inside the patched value")
|
||||||
|
{
|
||||||
|
// the patch is modified-while-iterated if it is not copied first: it is a
|
||||||
|
// descendant of the value it is applied to, which only the parent pointers
|
||||||
|
// (JSON_DIAGNOSTICS) reveal
|
||||||
|
json j =
|
||||||
|
{
|
||||||
|
{"patch", {{{"op", "add"}, {"path", "/x"}, {"value", 1}}, {{"op", "remove"}, {"path", "/patch/0"}}}},
|
||||||
|
{"y", 2}
|
||||||
|
};
|
||||||
|
|
||||||
|
j.patch_inplace(j["patch"]);
|
||||||
|
|
||||||
|
CHECK(j.size() == 3);
|
||||||
|
CHECK(j["x"] == 1);
|
||||||
|
CHECK(j["y"] == 2);
|
||||||
|
CHECK(j["patch"].size() == 1);
|
||||||
|
CHECK(j["patch"][0]["op"] == "remove");
|
||||||
|
}
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
#if JSON_HAS_RANGES
|
#if JSON_HAS_RANGES
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
@@ -665,6 +667,23 @@ TEST_CASE("iterators 2")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("reverse iterators of a const value")
|
||||||
|
{
|
||||||
|
const json j_array = {1, 2, 3};
|
||||||
|
const json j_object = {{"one", 1}, {"two", 2}};
|
||||||
|
|
||||||
|
// rbegin() and rend() of a const value are crbegin() and crend()
|
||||||
|
CHECK(j_array.rbegin() == j_array.crbegin());
|
||||||
|
CHECK(j_array.rend() == j_array.crend());
|
||||||
|
CHECK(*j_array.rbegin() == 3);
|
||||||
|
CHECK(std::distance(j_array.rbegin(), j_array.rend()) == 3);
|
||||||
|
|
||||||
|
CHECK(j_object.rbegin() == j_object.crbegin());
|
||||||
|
CHECK(j_object.rend() == j_object.crend());
|
||||||
|
CHECK(j_object.rbegin().key() == "two");
|
||||||
|
CHECK(std::distance(j_object.rbegin(), j_object.rend()) == 2);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("reverse iterator arithmetic")
|
SECTION("reverse iterator arithmetic")
|
||||||
{
|
{
|
||||||
json j_object = {{"one", 1}, {"two", 2}, {"three", 3}};
|
json j_object = {{"one", 1}, {"two", 2}, {"three", 3}};
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ using nlohmann::json;
|
|||||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
|
|
||||||
@@ -2453,3 +2455,99 @@ TEST_CASE("JSON patch - diff behavior documented in the API reference")
|
|||||||
CHECK(json::diff(json(nullptr), json::array()) == R"([{"op": "replace", "path": "", "value": []}])"_json);
|
CHECK(json::diff(json(nullptr), json::array()) == R"([{"op": "replace", "path": "", "value": []}])"_json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("JSON patch - operations that are left to json_pointer")
|
||||||
|
{
|
||||||
|
// the usual operations on a plain pointer are applied without building a
|
||||||
|
// json_pointer; the following are not plain, and some are errors
|
||||||
|
SECTION("a path without a slash")
|
||||||
|
{
|
||||||
|
const json j = {{"a", 1}};
|
||||||
|
|
||||||
|
CHECK_THROWS_AS(j.patch(R"([{"op": "add", "path": "abc", "value": 1}])"_json), json::parse_error&);
|
||||||
|
CHECK_THROWS_AS(j.patch(R"([{"op": "remove", "path": "abc"}])"_json), json::parse_error&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("remove with an escaped reference token")
|
||||||
|
{
|
||||||
|
const json j = {{"a/b", 1}, {"c~d", 2}, {"e", 3}};
|
||||||
|
|
||||||
|
CHECK(j.patch(R"([{"op": "remove", "path": "/a~1b"}])"_json) == json({{"c~d", 2}, {"e", 3}}));
|
||||||
|
CHECK(j.patch(R"([{"op": "remove", "path": "/c~0d"}])"_json) == json({{"a/b", 1}, {"e", 3}}));
|
||||||
|
CHECK_THROWS_AS(j.patch(R"([{"op": "remove", "path": "/missing~1key"}])"_json), json::out_of_range&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a path through a primitive value")
|
||||||
|
{
|
||||||
|
const json j = {{"a", 1}};
|
||||||
|
|
||||||
|
CHECK_THROWS_AS(j.patch(R"([{"op": "test", "path": "/a/b", "value": 1}])"_json), json::other_error&);
|
||||||
|
CHECK_THROWS_AS(j.patch(R"([{"op": "replace", "path": "/a/b", "value": 1}])"_json), json::out_of_range&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("JSON patch - diff of objects with many members")
|
||||||
|
{
|
||||||
|
// more members than diff() keeps without allocating
|
||||||
|
for (const std::size_t members :
|
||||||
|
{
|
||||||
|
16u, 17u, 40u
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(members)
|
||||||
|
|
||||||
|
json source = json::object();
|
||||||
|
json target = json::object();
|
||||||
|
for (std::size_t i = 0; i < members; ++i)
|
||||||
|
{
|
||||||
|
const std::string key = "key" + std::to_string(i);
|
||||||
|
source[key] = i;
|
||||||
|
// every third member differs
|
||||||
|
target[key] = (i % 3 == 0) ? i + 1 : i;
|
||||||
|
}
|
||||||
|
// the same keys, a removed and an added one
|
||||||
|
source["removed"] = true;
|
||||||
|
target["added"] = false;
|
||||||
|
|
||||||
|
const json patch = json::diff(source, target);
|
||||||
|
CHECK(source.patch(patch) == target);
|
||||||
|
CHECK(patch.size() == ((members + 2) / 3) + 2);
|
||||||
|
|
||||||
|
const nlohmann::ordered_json ordered_source = source;
|
||||||
|
const nlohmann::ordered_json ordered_target = target;
|
||||||
|
CHECK(ordered_source.patch(nlohmann::ordered_json::diff(ordered_source, ordered_target)) == ordered_target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("JSON patch - diff of leaf values below the bound of the recursive descent")
|
||||||
|
{
|
||||||
|
// the values at the innermost level are diffed without recursion: equal
|
||||||
|
// leaves yield nothing, leaves of different types and different leaves
|
||||||
|
// are replaced
|
||||||
|
const std::size_t depth = 200;
|
||||||
|
|
||||||
|
json source = {1, "text", 3, {{"x", 1}, {"y", 2}}, true, nullptr};
|
||||||
|
json target = {1, 2, 4, {{"x", 1}, {"y", 3}}, true, nullptr};
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
source = json::array({std::move(source)});
|
||||||
|
target = json::array({std::move(target)});
|
||||||
|
}
|
||||||
|
|
||||||
|
const json patch = json::diff(source, target);
|
||||||
|
CHECK(patch.size() == 3);
|
||||||
|
CHECK(source.patch(patch) == target);
|
||||||
|
|
||||||
|
// the same with objects as the outer levels
|
||||||
|
json object_source = {{"equal", 1}, {"type", "text"}, {"value", 3}};
|
||||||
|
json object_target = {{"equal", 1}, {"type", 2}, {"value", 4}};
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
object_source = json::object({{"a", std::move(object_source)}});
|
||||||
|
object_target = json::object({{"a", std::move(object_target)}});
|
||||||
|
}
|
||||||
|
|
||||||
|
const json object_patch = json::diff(object_source, object_target);
|
||||||
|
CHECK(object_patch.size() == 2);
|
||||||
|
CHECK(object_source.patch(object_patch) == object_target);
|
||||||
|
}
|
||||||
@@ -106,6 +106,12 @@ TEST_CASE("JSON pointers")
|
|||||||
CHECK_THROWS_WITH_AS(p.pop_back(),
|
CHECK_THROWS_WITH_AS(p.pop_back(),
|
||||||
"[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&);
|
"[json.exception.out_of_range.405] JSON pointer has no parent", json::out_of_range&);
|
||||||
|
|
||||||
|
// the top of a pointer is its first reference token
|
||||||
|
const json::json_pointer nonempty("/foo/bar/0");
|
||||||
|
CHECK(nonempty.top() == json::json_pointer("/foo"));
|
||||||
|
CHECK(nonempty.top().to_string() == "/foo");
|
||||||
|
CHECK(json::json_pointer("/a~1b").top() == json::json_pointer("/a~1b"));
|
||||||
|
|
||||||
SECTION("array index error")
|
SECTION("array index error")
|
||||||
{
|
{
|
||||||
json v = {1, 2, 3, 4};
|
json v = {1, 2, 3, 4};
|
||||||
@@ -697,6 +703,16 @@ TEST_CASE("JSON pointers")
|
|||||||
CHECK_THROWS_WITH_AS(j_error.unflatten(),
|
CHECK_THROWS_WITH_AS(j_error.unflatten(),
|
||||||
"[json.exception.type_error.313] invalid value to unflatten", json::type_error&);
|
"[json.exception.type_error.313] invalid value to unflatten", json::type_error&);
|
||||||
|
|
||||||
|
// error for an invalid escape sequence in a key
|
||||||
|
CHECK_THROWS_WITH_AS(json({{"/a~2", 1}}).unflatten(),
|
||||||
|
"[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(json({{"/a/b", 1}, {"/c~", 1}}).unflatten(),
|
||||||
|
"[json.exception.parse_error.108] parse error: escape character '~' must be followed with '0' or '1'", json::parse_error&);
|
||||||
|
|
||||||
|
// error for an element of an array that is also a container
|
||||||
|
CHECK_THROWS_WITH_AS(json({{"/a/0", 1}, {"/a/0/x", 2}}).unflatten(),
|
||||||
|
"[json.exception.type_error.313] invalid value to unflatten", json::type_error&);
|
||||||
|
|
||||||
// explicit roundtrip check
|
// explicit roundtrip check
|
||||||
CHECK(j.flatten().unflatten() == j);
|
CHECK(j.flatten().unflatten() == j);
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,11 @@ using json_view_test::has_duplicate_keys;
|
|||||||
#include <ios>
|
#include <ios>
|
||||||
#include <istream>
|
#include <istream>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
#include <functional>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <new>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -98,6 +100,37 @@ template<typename View>
|
|||||||
using bool_conversion_t = decltype(static_cast<bool>(std::declval<View>()));
|
using bool_conversion_t = decltype(static_cast<bool>(std::declval<View>()));
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// an allocator that never has room
|
||||||
|
template<typename T>
|
||||||
|
struct failing_allocator
|
||||||
|
{
|
||||||
|
using value_type = T;
|
||||||
|
|
||||||
|
failing_allocator() = default;
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
explicit failing_allocator(const failing_allocator<U>& /*other*/) noexcept {}
|
||||||
|
|
||||||
|
T* allocate(std::size_t /*n*/) // NOLINT(readability-convert-member-functions-to-static)
|
||||||
|
{
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocate(T* /*p*/, std::size_t /*n*/) noexcept {} // NOLINT(readability-convert-member-functions-to-static)
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
bool operator==(const failing_allocator<U>& /*other*/) const noexcept
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
bool operator!=(const failing_allocator<U>& /*other*/) const noexcept
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// the exception parse() throws for a text, or "" if it accepts it
|
// the exception parse() throws for a text, or "" if it accepts it
|
||||||
std::string parse_exception(const std::string& text, bool comments = false, bool trailing_commas = false)
|
std::string parse_exception(const std::string& text, bool comments = false, bool trailing_commas = false)
|
||||||
{
|
{
|
||||||
@@ -235,7 +268,9 @@ TEST_CASE("json_view")
|
|||||||
{
|
{
|
||||||
"", " ", "[", "]", "{", "[1,]", "{\"a\":1,}", "[1 2]", "{\"a\" 1}", "{1:2}", "tru", "nul", "fals", "truex", "-", "01", "1.", ".5", "1e",
|
"", " ", "[", "]", "{", "[1,]", "{\"a\":1,}", "[1 2]", "{\"a\" 1}", "{1:2}", "tru", "nul", "fals", "truex", "-", "01", "1.", ".5", "1e",
|
||||||
"\"", "\"abc", "\"\\x\"", "\"\\u12\"", "\"\\ud800\"", "\"\\udc00\"", "\"\x01\"", "\"\xff\"", "\"\xc3\"", "[1]x", "/", "/*", "[\n 1,\n x\n]", // NOLINT(modernize-raw-string-literal)
|
"\"", "\"abc", "\"\\x\"", "\"\\u12\"", "\"\\ud800\"", "\"\\udc00\"", "\"\x01\"", "\"\xff\"", "\"\xc3\"", "[1]x", "/", "/*", "[\n 1,\n x\n]", // NOLINT(modernize-raw-string-literal)
|
||||||
"1e400", "-1e400", "[1.7976931348623159e308]", "{\"a\":\n{\"b\": [1, 2,\n 3 x]}}"
|
"1e400", "-1e400", "[1.7976931348623159e308]", "{\"a\":\n{\"b\": [1, 2,\n 3 x]}}",
|
||||||
|
// (an error after every kind of value: the error is found by replaying the parser)
|
||||||
|
"[null, true, -1, 1, 1.5, \"s\", {\"a\": 1} 2]", "{\"a\": null, \"b\": -1, \"c\": 1.5, \"d\": \"s\", \"e\": {}, 1}"
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(text)
|
CAPTURE(text)
|
||||||
@@ -368,6 +403,10 @@ TEST_CASE("json_view")
|
|||||||
CHECK(from_stream.root().materialize() == expected);
|
CHECK(from_stream.root().materialize() == expected);
|
||||||
const std::list<char> list(text.begin(), text.end());
|
const std::list<char> list(text.begin(), text.end());
|
||||||
CHECK(json_document::parse(list.begin(), list.end()).owns_source());
|
CHECK(json_document::parse(list.begin(), list.end()).owns_source());
|
||||||
|
// (an input that is not valid)
|
||||||
|
const std::list<char> bad_list{'[', '1', ','};
|
||||||
|
json_document from_bad_list;
|
||||||
|
CHECK_THROWS_AS(from_bad_list = json_document::parse(bad_list.begin(), bad_list.end()), json::parse_error&);
|
||||||
CHECK(materialized<json_document>(list.begin(), list.end()) == expected);
|
CHECK(materialized<json_document>(list.begin(), list.end()) == expected);
|
||||||
|
|
||||||
// iterator pairs: pointers are borrowed, and so are contiguous library
|
// iterator pairs: pointers are borrowed, and so are contiguous library
|
||||||
@@ -1511,6 +1550,17 @@ TEST_CASE("json_view values")
|
|||||||
const json_document dup = json_document::parse(R"({"a":1,"a":2})");
|
const json_document dup = json_document::parse(R"({"a":1,"a":2})");
|
||||||
CHECK((dup.root().get<std::map<std::string, int>>() == std::map<std::string, int> {{"a", 2}}));
|
CHECK((dup.root().get<std::map<std::string, int>>() == std::map<std::string, int> {{"a", 2}}));
|
||||||
|
|
||||||
|
// a map of views from something that is not an object
|
||||||
|
using view_map = std::map<std::string, json_view>;
|
||||||
|
const json_document not_an_object = json_document::parse("[1,2]");
|
||||||
|
CHECK_THROWS_WITH_AS(not_an_object.root().get<view_map>(), json_view_test::message("type_error.302", "", 0, 5, "type must be object, but is array").c_str(), json::type_error&);
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// (and one that cannot allocate its nodes)
|
||||||
|
using failing_map = std::map<std::string, json_view, std::less<std::string>, failing_allocator<std::pair<const std::string, json_view>>>;
|
||||||
|
const json_document one_member = json_document::parse(R"({"a":1})");
|
||||||
|
CHECK_THROWS_AS(one_member.root().get<failing_map>(), std::bad_alloc&);
|
||||||
|
#endif
|
||||||
|
|
||||||
const json_view invalid{};
|
const json_view invalid{};
|
||||||
CHECK_THROWS_WITH_AS(invalid.get<int>(), "[json.exception.type_error.302] type must be number, but is discarded", json::type_error&);
|
CHECK_THROWS_WITH_AS(invalid.get<int>(), "[json.exception.type_error.302] type must be number, but is discarded", json::type_error&);
|
||||||
CHECK(invalid.get<json>().is_discarded());
|
CHECK(invalid.get<json>().is_discarded());
|
||||||
|
|||||||
@@ -692,7 +692,7 @@ TEST_CASE("json_view builder: fraction digits")
|
|||||||
check_same(number);
|
check_same(number);
|
||||||
check_same("[" + number + "]");
|
check_same("[" + number + "]");
|
||||||
std::string pair;
|
std::string pair;
|
||||||
pair.reserve((number.size() * 2) + 3);
|
pair.reserve(number.size() * 2 + 3);
|
||||||
pair += '[';
|
pair += '[';
|
||||||
pair += number;
|
pair += number;
|
||||||
pair += ',';
|
pair += ',';
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::json_document;
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// These tests were split off unit-json_view_compare.cpp, whose object file got
|
||||||
|
// too large for the MinGW assembler ("file too big").
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// an object type that does not enumerate its members in ascending order of keys
|
||||||
|
template<typename Key, typename Value, typename /*Compare*/, typename Allocator>
|
||||||
|
using descending_object = std::map<Key, Value, std::greater<Key>, Allocator>;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("json_view comparison of differing values")
|
||||||
|
{
|
||||||
|
SECTION("values that differ in their first bytes, or are written differently")
|
||||||
|
{
|
||||||
|
const auto same = [](const char* x, const char* y)
|
||||||
|
{
|
||||||
|
const json_document dx = json_document::parse(x);
|
||||||
|
const json_document dy = json_document::parse(y);
|
||||||
|
const bool equal = dx.root() == dy.root();
|
||||||
|
CHECK(equal == (json::parse(x) == json::parse(y)));
|
||||||
|
CHECK(equal == (dx.root() == json::parse(y)));
|
||||||
|
const nlohmann::json_editable_document ex = nlohmann::json_editable_document::parse(x);
|
||||||
|
CHECK(equal == (ex.root() == dy.root()));
|
||||||
|
return equal;
|
||||||
|
};
|
||||||
|
// booleans
|
||||||
|
CHECK(!same("true", "false"));
|
||||||
|
CHECK(!same("[true]", "[false]"));
|
||||||
|
CHECK(!same(R"({"a":false,"b":[1]})", R"({"a":true,"b":[1]})"));
|
||||||
|
CHECK(same(R"({"a":true,"b":[1]})", R"({"a":true,"b":[1]})"));
|
||||||
|
// strings and keys of equal length that differ in the first eight bytes (they are compared in words)
|
||||||
|
CHECK(!same(R"("abcdefghijklmnopqrstuvwxyz")", R"("Xbcdefghijklmnopqrstuvwxyz")"));
|
||||||
|
CHECK(!same(R"(["abcdefghijklmnopq",1])", R"(["abcdefghXjklmnopq",1])"));
|
||||||
|
CHECK(!same(R"({"abcdefghijklmnopq":1})", R"({"Xbcdefghijklmnopq":1})"));
|
||||||
|
CHECK(!same(R"({"abcdefghijklmnopq":[1]})", R"({"abcdefghijklmnopX":[1]})"));
|
||||||
|
// floats of one layout and different tokens are the same value
|
||||||
|
CHECK(same("[1.5e0, 2]", "[1.5E0, 2]"));
|
||||||
|
CHECK(same("[1.5e0, 2]", "[1.5e+0, 2]"));
|
||||||
|
CHECK(same("[1.50, 2]", "[1.5, 2]"));
|
||||||
|
CHECK(!same("[1.5e0, 2]", "[1.6e0, 2]"));
|
||||||
|
CHECK(!same("[1.5e0, 2]", "[1.5E1, 2]"));
|
||||||
|
CHECK(same(R"({"a":1.25e0,"b":[3]})", R"({"a":1.25E0,"b":[3]})"));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a basic_json whose members are not sorted")
|
||||||
|
{
|
||||||
|
// (an object type that is not an ordered map, and does not keep its members sorted either)
|
||||||
|
using descending_json = nlohmann::basic_json<descending_object>;
|
||||||
|
using descending_document = nlohmann::basic_json_document<descending_json>;
|
||||||
|
std::string text = "{";
|
||||||
|
for (char key = 'a'; key <= 'p'; ++key)
|
||||||
|
{
|
||||||
|
text += (key != 'a' ? ",\"" : "\"") + std::string(1, key) + "\":[" + std::to_string(key) + "]";
|
||||||
|
}
|
||||||
|
text += "}";
|
||||||
|
const descending_json j = descending_json::parse(text);
|
||||||
|
const descending_document d = descending_document::parse(text);
|
||||||
|
CHECK(d.root() == j);
|
||||||
|
CHECK(j == d.root());
|
||||||
|
descending_json other = j;
|
||||||
|
other["k"] = 0;
|
||||||
|
CHECK(d.root() != other);
|
||||||
|
CHECK(other != d.root());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,6 +101,10 @@ TEST_CASE("json_view dump")
|
|||||||
CHECK(d.root().dump(0, ' ', false, json_view::number_format::source) == "[\n1.50,\n1E2,\n-0,\n-0.0,\n123456789012345678901234567890,\n18446744073709551615,\n-9223372036854775808,\n0.1,\n1e-7,\n5e-324\n]");
|
CHECK(d.root().dump(0, ' ', false, json_view::number_format::source) == "[\n1.50,\n1E2,\n-0,\n-0.0,\n123456789012345678901234567890,\n18446744073709551615,\n-9223372036854775808,\n0.1,\n1e-7,\n5e-324\n]");
|
||||||
CHECK(d.root().dump(-1, ' ', true, json_view::number_format::source) == "[1.50,1E2,-0,-0.0,123456789012345678901234567890,18446744073709551615,-9223372036854775808,0.1,1e-7,5e-324]");
|
CHECK(d.root().dump(-1, ' ', true, json_view::number_format::source) == "[1.50,1E2,-0,-0.0,123456789012345678901234567890,18446744073709551615,-9223372036854775808,0.1,1e-7,5e-324]");
|
||||||
|
|
||||||
|
CHECK(d.root().dump(0, ' ', true, json_view::number_format::source) == d.root().dump(0, ' ', false, json_view::number_format::source));
|
||||||
|
const json_document escaped = json_document::parse("[\"\xC3\xA9\", 1.50, {\"\xF0\x9F\x98\x80\": 1E2}]");
|
||||||
|
CHECK(escaped.root().dump(1, ' ', true, json_view::number_format::source) == "[\n \"\\u00e9\",\n 1.50,\n {\n \"\\ud83d\\ude00\": 1E2\n }\n]");
|
||||||
|
|
||||||
// float tokens of up to 17 significant digits in every spelling: those
|
// float tokens of up to 17 significant digits in every spelling: those
|
||||||
// of at most 15 digits are written from their digits, the others
|
// of at most 15 digits are written from their digits, the others
|
||||||
// through the conversion; both as dump() writes them
|
// through the conversion; both as dump() writes them
|
||||||
@@ -232,6 +236,29 @@ TEST_CASE("json_view dump")
|
|||||||
CHECK(editable.root().dump() == expected.dump());
|
CHECK(editable.root().dump() == expected.dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tokens of 16 and 17 digits with few digits after the point, and with a value of 0, which are
|
||||||
|
// converted by the general algorithm; padded, as a token near the end of the text is not read in this way
|
||||||
|
{
|
||||||
|
const std::string padding(40, ' ');
|
||||||
|
for (const char* token :
|
||||||
|
{
|
||||||
|
"123456789012.3456", "-123456789012.3456", "1234567890123.456", "12345678901234.56", "123456789012345.6", "1234567890123456.0",
|
||||||
|
"0.0000000000000000", "-0.0000000000000000", "0.000000000000000", "1234567890123.4567", "99999999999999.999"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(token)
|
||||||
|
const std::string padded = std::string("[") + token + padding + "]";
|
||||||
|
const json_document padded_document = json_document::parse(padded);
|
||||||
|
const json padded_json = json::parse(padded);
|
||||||
|
CHECK(padded_document.root()[0].get<double>() == padded_json[0].get<double>());
|
||||||
|
CHECK(padded_document.root().dump() == padded_json.dump());
|
||||||
|
// a float document converts the tokens as well
|
||||||
|
using json_float = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
||||||
|
const nlohmann::basic_json_document<json_float> float_doc = nlohmann::basic_json_document<json_float>::parse(padded);
|
||||||
|
CHECK(float_doc.root()[0].get<float>() == json_float::parse(padded)[0].get<float>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// random doubles, written as parse() and dump() would
|
// random doubles, written as parse() and dump() would
|
||||||
std::mt19937_64 rng(1170); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
std::mt19937_64 rng(1170); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
||||||
std::string many = "[";
|
std::string many = "[";
|
||||||
|
|||||||
@@ -214,6 +214,24 @@ TEST_CASE("json_view dump: escaping, nesting, and output buffers")
|
|||||||
CHECK(d.root()["a"].dump(-1, ' ', true).size() == 2 + (5000 * 6));
|
CHECK(d.root()["a"].dump(-1, ' ', true).size() == 2 + (5000 * 6));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("the estimate for a value that is followed by many decoded strings")
|
||||||
|
{
|
||||||
|
// (the end of the value in the source is not known when the nodes after it have decoded strings)
|
||||||
|
std::string text = "[7";
|
||||||
|
for (int i = 0; i < 20; ++i)
|
||||||
|
{
|
||||||
|
text += R"(,"\n")";
|
||||||
|
}
|
||||||
|
text += ",8]";
|
||||||
|
const json_document d = json_document::parse(text);
|
||||||
|
CHECK(d.root()[0].dump() == "7");
|
||||||
|
CHECK(d.root()[1].dump() == R"("\n")");
|
||||||
|
CHECK(d.root().dump() == json::parse(text).dump());
|
||||||
|
// the same with a container
|
||||||
|
const json_document nested = json_document::parse(R"([[7],)" + text.substr(3));
|
||||||
|
CHECK(nested.root()[0].dump() == "[7]");
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("ensure_ascii with strings of the source, decoded strings, and edited strings")
|
SECTION("ensure_ascii with strings of the source, decoded strings, and edited strings")
|
||||||
{
|
{
|
||||||
// plain ASCII, non-ASCII, escaped (decoded), and mixed strings, as values and as keys;
|
// plain ASCII, non-ASCII, escaped (decoded), and mixed strings, as values and as keys;
|
||||||
@@ -259,6 +277,28 @@ TEST_CASE("json_view dump: escaping, nesting, and output buffers")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("the length of the run that ensure_ascii copies")
|
||||||
|
{
|
||||||
|
// (dump() only asks for strings with a byte to escape, so a run that ends the string is checked here)
|
||||||
|
for (const std::size_t length :
|
||||||
|
{
|
||||||
|
std::size_t{0}, std::size_t{1}, std::size_t{15}, std::size_t{16}, std::size_t{17}, std::size_t{31}, std::size_t{32}, std::size_t{48}, std::size_t{50}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(length)
|
||||||
|
// a run of printable characters, followed by a byte that is not copied and more text
|
||||||
|
std::string text(length, 'a');
|
||||||
|
text += "\"abcdefghijklmnopqrstuvwxyz";
|
||||||
|
const auto* const bytes = reinterpret_cast<const unsigned char*>(text.data()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
const auto* const end = bytes + text.size();
|
||||||
|
CHECK(nlohmann::detail::view::ascii_copyable_run(bytes, length, end) == length);
|
||||||
|
CHECK(nlohmann::detail::view::ascii_copyable_run(bytes, length + 1, end) == length);
|
||||||
|
CHECK(nlohmann::detail::view::ascii_copyable_run(bytes, length + 5, end) == length);
|
||||||
|
// the end of the buffer right after the run
|
||||||
|
CHECK(nlohmann::detail::view::ascii_copyable_run(bytes, length, bytes + length) == length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("ensure_ascii with DEL or a non-ASCII character at every position")
|
SECTION("ensure_ascii with DEL or a non-ASCII character at every position")
|
||||||
{
|
{
|
||||||
// the check whether a string of the source has no byte >= 0x7F reads
|
// the check whether a string of the source has no byte >= 0x7F reads
|
||||||
|
|||||||
@@ -547,6 +547,10 @@ TEST_CASE("json_view edits: errors")
|
|||||||
CHECK_THROWS_WITH_AS(d.set(json::json_pointer("/a/01"), 1), "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
|
CHECK_THROWS_WITH_AS(d.set(json::json_pointer("/a/01"), 1), "[json.exception.parse_error.106] parse error: array index '01' must not begin with '0'", json::parse_error&);
|
||||||
CHECK_THROWS_WITH_AS(d.set(root, json_editable_view()), "[json.exception.type_error.302] type must be a value, but is discarded", json::type_error&);
|
CHECK_THROWS_WITH_AS(d.set(root, json_editable_view()), "[json.exception.type_error.302] type must be a value, but is discarded", json::type_error&);
|
||||||
CHECK_THROWS_WITH_AS(d.set(root, json::binary({1, 2})), "[json.exception.type_error.319] cannot store a binary value in a json_document", json::type_error&);
|
CHECK_THROWS_WITH_AS(d.set(root, json::binary({1, 2})), "[json.exception.type_error.319] cannot store a binary value in a json_document", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(d.set(root, json(json::value_t::discarded)), "[json.exception.type_error.302] type must be a value, but is discarded", json::type_error&);
|
||||||
|
// (an index that does not fit the document, in a null: basic_json would pad it with 4 Gi nulls)
|
||||||
|
CHECK_THROWS_AS(d.set(json::json_pointer("/z/4294967295"), 1), json::out_of_range&);
|
||||||
|
CHECK(root["z"].is_null());
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
// invalid UTF-8 is rejected when it enters the document, with the error
|
// invalid UTF-8 is rejected when it enters the document, with the error
|
||||||
@@ -572,6 +576,19 @@ TEST_CASE("json_view edits: errors")
|
|||||||
static_cast<void>(other);
|
static_cast<void>(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("json_view edits: a container that was added is replaced by a scalar")
|
||||||
|
{
|
||||||
|
// the view of an element that push_back() added is that of the new value itself, not of
|
||||||
|
// the slot that links to it: replacing it by a scalar has to find the container it is in
|
||||||
|
json_editable_document d = json_editable_document::parse(R"({"a": [1]})");
|
||||||
|
d.push_back(d.root()["a"], json::array({2, 3}));
|
||||||
|
d.push_back(d.root()["a"], json::object({{"k", json::array({4, 5})}}));
|
||||||
|
d.set(d.root()["a"][1], 6);
|
||||||
|
d.set(d.root()["a"][2]["k"], "s");
|
||||||
|
CHECK(d.root().dump() == R"({"a":[1,6,{"k":"s"}]})");
|
||||||
|
CHECK(d.root().materialize() == json::parse(R"({"a":[1,6,{"k":"s"}]})"));
|
||||||
|
}
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
TEST_CASE("json_view edits: the size of a text arena")
|
TEST_CASE("json_view edits: the size of a text arena")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -277,6 +277,17 @@ TEST_CASE("json_view from_json")
|
|||||||
CHECK(true);
|
CHECK(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("a map with enum keys from a view that is not an object")
|
||||||
|
{
|
||||||
|
// an object is read with the keys as the enum's names; any other
|
||||||
|
// value is read as an array of pairs, like a map of any other key type
|
||||||
|
const json_document d = json_document::parse(R"({"pairs": [["red", 1], ["green", 2]], "number": 1})");
|
||||||
|
const json j = d.root().materialize();
|
||||||
|
const json_view v = d.root();
|
||||||
|
CHECK(same<std::map<color, int>>(v, j, "pairs"));
|
||||||
|
CHECK((v["pairs"].get<std::map<color, int>>() == std::map<color, int> {{color::red, 1}, {color::green, 2}}));
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("std types: the same values as from the materialized value")
|
SECTION("std types: the same values as from the materialized value")
|
||||||
{
|
{
|
||||||
const json_document d = json_document::parse(R"({"ints": [3, 1, 2, 1], "strings": ["b", "a", "b"], "pairs": [[1, "x"], [2, "y"]],
|
const json_document d = json_document::parse(R"({"ints": [3, 1, 2, 1], "strings": ["b", "a", "b"], "pairs": [[1, "x"], [2, "y"]],
|
||||||
|
|||||||
@@ -819,6 +819,13 @@ TEST_CASE("json_view images: errors")
|
|||||||
static_cast<void>(d);
|
static_cast<void>(d);
|
||||||
}) == prefix + "too short");
|
}) == prefix + "too short");
|
||||||
|
|
||||||
|
// an image that is moved into the document is checked as well
|
||||||
|
CHECK(exception_of([&]
|
||||||
|
{
|
||||||
|
const json_document d = json_document::load(std::vector<std::uint8_t>(10, 0));
|
||||||
|
static_cast<void>(d);
|
||||||
|
}) == prefix + "too short");
|
||||||
|
|
||||||
std::vector<std::uint8_t> bad = image;
|
std::vector<std::uint8_t> bad = image;
|
||||||
bad[0] = 'X';
|
bad[0] = 'X';
|
||||||
CHECK(load_result(bad, image_check::full) == prefix + "unknown format");
|
CHECK(load_result(bad, image_check::full) == prefix + "unknown format");
|
||||||
|
|||||||
@@ -333,6 +333,62 @@ TEST_CASE("json_view large objects")
|
|||||||
CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t)));
|
CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("a lookup of a missing key stops at the end of the probe window")
|
||||||
|
{
|
||||||
|
// Every slot of the first half of the table holds a key, each at its home slot, so
|
||||||
|
// that the table is built; a key that is not in the object but whose home slot is
|
||||||
|
// the first slot finds a full window of slots with other keys.
|
||||||
|
constexpr std::size_t members = 128;
|
||||||
|
constexpr std::size_t table_size = 256; // the table size for 128 members: the next power of two >= 256
|
||||||
|
for (const std::size_t prefix_length :
|
||||||
|
{
|
||||||
|
std::size_t{0}, std::size_t{20}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(prefix_length)
|
||||||
|
std::vector<std::string> keys(members);
|
||||||
|
std::vector<bool> taken(members, false);
|
||||||
|
std::string missing;
|
||||||
|
std::size_t found = 0;
|
||||||
|
for (std::uint64_t counter = 0; found < members || missing.empty(); ++counter)
|
||||||
|
{
|
||||||
|
std::string key(prefix_length, 'p');
|
||||||
|
std::uint64_t x = counter;
|
||||||
|
for (std::size_t i = 0; i < 8; ++i, x /= 26)
|
||||||
|
{
|
||||||
|
key += static_cast<char>('a' + (x % 26));
|
||||||
|
}
|
||||||
|
const std::size_t slot = nlohmann::detail::conditional_static_cast<std::size_t>(nlohmann::detail::view::key_hash(key.data(), key.size())) & (table_size - 1);
|
||||||
|
if (slot < members && !taken[slot])
|
||||||
|
{
|
||||||
|
taken[slot] = true;
|
||||||
|
keys[slot] = key;
|
||||||
|
++found;
|
||||||
|
}
|
||||||
|
else if (slot == 0 && missing.empty())
|
||||||
|
{
|
||||||
|
missing = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string text = "{";
|
||||||
|
for (std::size_t i = 0; i < members; ++i)
|
||||||
|
{
|
||||||
|
text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i);
|
||||||
|
}
|
||||||
|
text += "}";
|
||||||
|
const json_document d = json_document::parse(text);
|
||||||
|
const json_view v = d.root();
|
||||||
|
for (std::size_t i = 0; i < members; ++i)
|
||||||
|
{
|
||||||
|
CAPTURE(i)
|
||||||
|
CHECK(v[keys[i]].get<std::size_t>() == i);
|
||||||
|
}
|
||||||
|
CHECK(!v.contains(missing));
|
||||||
|
CHECK(v.find(missing) == v.end());
|
||||||
|
CHECK(v.count(missing) == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("keys that differ in their last bytes are spread over the table")
|
SECTION("keys that differ in their last bytes are spread over the table")
|
||||||
{
|
{
|
||||||
// The hash must spread the usual families of keys (numbered names, hex
|
// The hash must spread the usual families of keys (numbered names, hex
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ using nlohmann::ordered_json;
|
|||||||
using nlohmann::json_document;
|
using nlohmann::json_document;
|
||||||
using nlohmann::json_editable_document;
|
using nlohmann::json_editable_document;
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -427,6 +428,31 @@ TEST_CASE("json_view materialize() source positions")
|
|||||||
CHECK(same_positions(m[std::string("a") + '\n'][2], j[std::string("a") + '\n'][2]));
|
CHECK(same_positions(m[std::string("a") + '\n'][2], j[std::string("a") + '\n'][2]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("a damaged text: the end of a container is not found")
|
||||||
|
{
|
||||||
|
// (the text of an image is not checked with image_check::none)
|
||||||
|
const std::string array_text = "[1,2]";
|
||||||
|
const std::vector<std::uint8_t> original = json_document::parse(array_text).save();
|
||||||
|
const auto at = std::search(original.begin(), original.end(), array_text.begin(), array_text.end());
|
||||||
|
REQUIRE(at != original.end());
|
||||||
|
const auto closing = static_cast<std::size_t>(at - original.begin()) + array_text.size() - 1;
|
||||||
|
// not a closing bracket, a slash that starts no comment, and the end of the text
|
||||||
|
for (const char replacement :
|
||||||
|
{
|
||||||
|
'x', '/', ' '
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(replacement)
|
||||||
|
std::vector<std::uint8_t> damaged = original;
|
||||||
|
damaged[closing] = static_cast<std::uint8_t>(replacement);
|
||||||
|
const json_document d = json_document::load(damaged, json_document::image_check::none);
|
||||||
|
const json m = d.root().materialize();
|
||||||
|
CHECK(range(m) == "none");
|
||||||
|
CHECK(range(m[0]) == "1-2");
|
||||||
|
CHECK(range(m[1]) == "3-4");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
SECTION("a decoded string's position must be within the text")
|
SECTION("a decoded string's position must be within the text")
|
||||||
{
|
{
|
||||||
@@ -461,6 +487,7 @@ TEST_CASE("json_view exceptions name the path and the bytes of the value")
|
|||||||
{
|
{
|
||||||
"null", "true", "42", "\"s\"", "[]", "{}", "[1, [2, [3, []]]]", R"({"a": {"b": [1, {"c": null}]}, "d": "e"})",
|
"null", "true", "42", "\"s\"", "[]", "{}", "[1, [2, [3, []]]]", R"({"a": {"b": [1, {"c": null}]}, "d": "e"})",
|
||||||
R"({"a/b": {"~": [1]}, "\u0041\n": {"x": "\t"}, "": [{}]})",
|
R"({"a/b": {"~": [1]}, "\u0041\n": {"x": "\t"}, "": [{}]})",
|
||||||
|
R"({"a\u0000b": [1, "x"], "c": {"d\u0000": 2}})", // (the path of a key ends at a null byte, as in basic_json)
|
||||||
R"([1 /* ] */, {"a": [] // }
|
R"([1 /* ] */, {"a": [] // }
|
||||||
, "b": {} } , ])",
|
, "b": {} } , ])",
|
||||||
"\n\t[\r\n 1 ,\n {\n \"a\" : [ ]\n }\n]\n",
|
"\n\t[\r\n 1 ,\n {\n \"a\" : [ ]\n }\n]\n",
|
||||||
@@ -512,6 +539,49 @@ TEST_CASE("json_view exceptions name the path and the bytes of the value")
|
|||||||
CHECK(exception_of_call([&] { static_cast<void>(d.root()["a"][2]["n"][0].get<int>()); }) == expected);
|
CHECK(exception_of_call([&] { static_cast<void>(d.root()["a"][2]["n"][0].get<int>()); }) == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("views of values that are no longer part of the document, and of no value")
|
||||||
|
{
|
||||||
|
const std::string expected_string = "[json.exception.type_error.302] type must be number, but is string";
|
||||||
|
static_cast<void>(expected_string);
|
||||||
|
// (a value that is not found has no path, and with it no bytes)
|
||||||
|
{
|
||||||
|
json_editable_document d = json_editable_document::parse(R"(["x", ["y"]])");
|
||||||
|
const auto removed = d.root()[0];
|
||||||
|
d.erase(d.root(), 0);
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK(exception_of_call([&] { static_cast<void>(removed.get<int>()); }) == expected_string);
|
||||||
|
#endif
|
||||||
|
// (the root is a scalar now: the value cannot be in it)
|
||||||
|
d.set(d.root(), 5);
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK(exception_of_call([&] { static_cast<void>(removed.get<int>()); }) == expected_string);
|
||||||
|
#endif
|
||||||
|
CHECK(d.root().get<int>() == 5);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
json_editable_document d = json_editable_document::parse(R"(["x"])");
|
||||||
|
const auto removed = d.root()[0];
|
||||||
|
d.set(d.root(), 5);
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
CHECK(exception_of_call([&] { static_cast<void>(removed.get<int>()); }) == expected_string);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
// a view of no value has no context
|
||||||
|
CHECK(exception_of_call([&] { static_cast<void>(nlohmann::json_view().get<int>()); }) == "[json.exception.type_error.302] type must be number, but is discarded");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("an array whose last element was replaced by a new container has no bytes")
|
||||||
|
{
|
||||||
|
json_editable_document d = json_editable_document::parse(R"({"a": [1, [2]], "b": 1})");
|
||||||
|
d.set(d.root()["a"][1], json::array({3}));
|
||||||
|
std::string expected = "[json.exception.type_error.302] ";
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
expected += "(/a) ";
|
||||||
|
#endif
|
||||||
|
expected += "type must be number, but is array";
|
||||||
|
CHECK(exception_of_call([&] { static_cast<void>(d.root()["a"].get<int>()); }) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("deep documents with siblings after every nested container")
|
SECTION("deep documents with siblings after every nested container")
|
||||||
{
|
{
|
||||||
// the search for the path pushes and pops one frame per level (the
|
// the search for the path pushes and pops one frame per level (the
|
||||||
@@ -521,8 +591,7 @@ TEST_CASE("json_view exceptions name the path and the bytes of the value")
|
|||||||
std::string text = "7";
|
std::string text = "7";
|
||||||
for (int level = 0; level < 40; ++level)
|
for (int level = 0; level < 40; ++level)
|
||||||
{
|
{
|
||||||
text.insert(0, R"([[1, [2]], )");
|
text = R"([[1, [2]], )" + text + R"(, {"k": [3, {"l": [4]}]}, [5, [6, [7]]]])";
|
||||||
text += R"(, {"k": [3, {"l": [4]}]}, [5, [6, [7]]]])";
|
|
||||||
}
|
}
|
||||||
CAPTURE(text)
|
CAPTURE(text)
|
||||||
const json j = json::parse(text);
|
const json j = json::parse(text);
|
||||||
|
|||||||
@@ -12,7 +12,10 @@
|
|||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
TEST_CASE("tests on very large JSONs")
|
TEST_CASE("tests on very large JSONs")
|
||||||
{
|
{
|
||||||
@@ -140,6 +143,56 @@ TEST_CASE("tests on deeply nested JSONs")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("leaf values of every type below the bound of the recursive descent")
|
||||||
|
{
|
||||||
|
// the elements and members of the containers below the bound are
|
||||||
|
// copied one by one, whatever their type
|
||||||
|
const json leaves = json::array(
|
||||||
|
{
|
||||||
|
nullptr, true, false, -1, std::uint64_t{1}, 1.5, "text", json::binary({1, 2, 3})
|
||||||
|
});
|
||||||
|
const json members =
|
||||||
|
{
|
||||||
|
{"null", nullptr}, {"boolean", true}, {"integer", -1}, {"unsigned", std::uint64_t{1}},
|
||||||
|
{"float", 1.5}, {"string", "text"}, {"binary", json::binary({1, 2, 3})}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const bool objects :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(objects)
|
||||||
|
|
||||||
|
json j = objects ? members : leaves;
|
||||||
|
for (std::size_t i = 0; i < 300; ++i)
|
||||||
|
{
|
||||||
|
json wrapper = objects ? json::object() : json::array();
|
||||||
|
if (objects)
|
||||||
|
{
|
||||||
|
wrapper["a"] = std::move(j);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wrapper.push_back(std::move(j));
|
||||||
|
}
|
||||||
|
j = std::move(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
const json copy(j); // NOLINT(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||||
|
|
||||||
|
const json* current = ©
|
||||||
|
for (std::size_t i = 0; i < 300; ++i)
|
||||||
|
{
|
||||||
|
current = objects ? ¤t->at("a") : ¤t->front();
|
||||||
|
}
|
||||||
|
|
||||||
|
// the innermost value is shallow: it is safe to compare
|
||||||
|
CHECK(*current == (objects ? members : leaves));
|
||||||
|
CHECK(current->size() == (objects ? members.size() : leaves.size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("a value that is deep in one place only")
|
SECTION("a value that is deep in one place only")
|
||||||
{
|
{
|
||||||
json j = json::object();
|
json j = json::object();
|
||||||
|
|||||||
@@ -495,3 +495,206 @@ TEST_CASE("locale changes during a single dump() (#5709 item 3)")
|
|||||||
// fix #5597 made on the parser side
|
// fix #5597 made on the parser side
|
||||||
CHECK(buf.data == "[\"" + padding + "\",1234.5]");
|
CHECK(buf.data == "[\"" + padding + "\",1234.5]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// a token whose first data() call switches LC_NUMERIC, so that the locale
|
||||||
|
// changes between convert_float_locale_aware() looking up the decimal point
|
||||||
|
// and strtod reading the token (the race of #5198, made deterministic)
|
||||||
|
struct locale_switching_token
|
||||||
|
{
|
||||||
|
using value_type = char;
|
||||||
|
|
||||||
|
locale_switching_token(std::string text_, const char* switch_to_)
|
||||||
|
: text(std::move(text_)), switch_to(switch_to_)
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::size_t size() const
|
||||||
|
{
|
||||||
|
return text.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
char& operator[](std::size_t i)
|
||||||
|
{
|
||||||
|
return text[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
char* data()
|
||||||
|
{
|
||||||
|
if (!switched)
|
||||||
|
{
|
||||||
|
switched = std::setlocale(LC_NUMERIC, switch_to) != nullptr;
|
||||||
|
}
|
||||||
|
return &text[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string text;
|
||||||
|
const char* switch_to;
|
||||||
|
bool switched = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// set LC_NUMERIC to the first of @a names that is usable and has a decimal
|
||||||
|
// point of @a min_size bytes at least (and at most @a max_size)
|
||||||
|
bool use_decimal_point_locale(const std::vector<const char*>& names, std::size_t min_size, std::size_t max_size, const char*& used)
|
||||||
|
{
|
||||||
|
for (const char* name : names)
|
||||||
|
{
|
||||||
|
if (std::setlocale(LC_NUMERIC, name) == nullptr)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::size_t size = std::string(std::localeconv()->decimal_point).size();
|
||||||
|
if (size >= min_size && size <= max_size)
|
||||||
|
{
|
||||||
|
used = name;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert a token with convert_float_locale_aware(); the token must be given
|
||||||
|
// back as it was
|
||||||
|
template<typename FloatType>
|
||||||
|
FloatType convert_locale_aware(const std::string& text)
|
||||||
|
{
|
||||||
|
std::string token = text;
|
||||||
|
const std::size_t dot = token.find('.');
|
||||||
|
FloatType value{};
|
||||||
|
nlohmann::detail::convert_float_locale_aware(token, dot, value);
|
||||||
|
CHECK(token == text);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename FloatType>
|
||||||
|
void check_locale_aware_conversions()
|
||||||
|
{
|
||||||
|
CHECK(convert_locale_aware<FloatType>("3.5") == FloatType(3.5));
|
||||||
|
CHECK(convert_locale_aware<FloatType>("-0.25") == FloatType(-0.25));
|
||||||
|
CHECK(convert_locale_aware<FloatType>("1.25e2") == FloatType(125));
|
||||||
|
// no decimal point at all
|
||||||
|
CHECK(convert_locale_aware<FloatType>("12e1") == FloatType(120));
|
||||||
|
CHECK(convert_locale_aware<FloatType>("7") == FloatType(7));
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("convert_float_locale_aware")
|
||||||
|
{
|
||||||
|
const std::vector<const char*> single_byte_names = {"de_DE.UTF-8", "de_DE", "fr_FR.UTF-8", "fr_FR", "ru_RU.UTF-8"};
|
||||||
|
const std::vector<const char*> multi_byte_names = {"ar_EG.UTF-8", "ar_SA.UTF-8", "fa_IR.UTF-8", "ps_AF.UTF-8", "ar_EG", "fa_IR"};
|
||||||
|
const char* used = nullptr;
|
||||||
|
|
||||||
|
SECTION("decimal point '.'")
|
||||||
|
{
|
||||||
|
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||||
|
check_locale_aware_conversions<float>();
|
||||||
|
check_locale_aware_conversions<double>();
|
||||||
|
check_locale_aware_conversions<long double>();
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("single-byte decimal point other than '.'")
|
||||||
|
{
|
||||||
|
if (use_decimal_point_locale(single_byte_names, 1, 1, used) && std::localeconv()->decimal_point[0] != '.')
|
||||||
|
{
|
||||||
|
CAPTURE(used)
|
||||||
|
check_locale_aware_conversions<float>();
|
||||||
|
check_locale_aware_conversions<double>();
|
||||||
|
check_locale_aware_conversions<long double>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MESSAGE("no locale with a single-byte decimal point other than '.' is usable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("multi-byte decimal point")
|
||||||
|
{
|
||||||
|
if (use_decimal_point_locale(multi_byte_names, 2, 8, used))
|
||||||
|
{
|
||||||
|
CAPTURE(used)
|
||||||
|
check_locale_aware_conversions<float>();
|
||||||
|
check_locale_aware_conversions<double>();
|
||||||
|
check_locale_aware_conversions<long double>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MESSAGE("no locale with a multi-byte decimal point is usable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a token that is not converted completely")
|
||||||
|
{
|
||||||
|
// the locale does not change, so there is nothing to retry: the
|
||||||
|
// value up to the garbage is kept
|
||||||
|
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||||
|
std::string token = "1.5x";
|
||||||
|
double value = 0;
|
||||||
|
nlohmann::detail::convert_float_locale_aware(token, token.find('.'), value);
|
||||||
|
CHECK(value == 1.5);
|
||||||
|
CHECK(token == "1.5x");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the locale changes before the conversion")
|
||||||
|
{
|
||||||
|
if (!use_decimal_point_locale(single_byte_names, 1, 1, used) || std::localeconv()->decimal_point[0] == '.')
|
||||||
|
{
|
||||||
|
MESSAGE("no locale with a single-byte decimal point other than '.' is usable");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const std::string comma_locale = used;
|
||||||
|
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||||
|
|
||||||
|
// "C" -> comma locale: strtod stops at the '.', so the conversion is repeated
|
||||||
|
{
|
||||||
|
locale_switching_token token("1.5", comma_locale.c_str());
|
||||||
|
double value = 0;
|
||||||
|
nlohmann::detail::convert_float_locale_aware(token, 1, value);
|
||||||
|
if (token.switched)
|
||||||
|
{
|
||||||
|
CHECK(value == 1.5);
|
||||||
|
CHECK(token.text == "1.5");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the same for the other floating-point types
|
||||||
|
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||||
|
{
|
||||||
|
locale_switching_token token("2.25", comma_locale.c_str());
|
||||||
|
float value = 0;
|
||||||
|
nlohmann::detail::convert_float_locale_aware(token, 1, value);
|
||||||
|
if (token.switched)
|
||||||
|
{
|
||||||
|
CHECK(value == 2.25f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||||
|
{
|
||||||
|
locale_switching_token token("2.25", comma_locale.c_str());
|
||||||
|
long double value = 0;
|
||||||
|
nlohmann::detail::convert_float_locale_aware(token, 1, value);
|
||||||
|
if (token.switched)
|
||||||
|
{
|
||||||
|
CHECK(value == 2.25L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// comma locale -> multi-byte decimal point
|
||||||
|
const char* multi_byte = nullptr;
|
||||||
|
if (use_decimal_point_locale(multi_byte_names, 2, 8, multi_byte))
|
||||||
|
{
|
||||||
|
REQUIRE(std::setlocale(LC_NUMERIC, comma_locale.c_str()) != nullptr);
|
||||||
|
locale_switching_token token("1.5", multi_byte);
|
||||||
|
double value = 0;
|
||||||
|
nlohmann::detail::convert_float_locale_aware(token, 1, value);
|
||||||
|
if (token.switched)
|
||||||
|
{
|
||||||
|
CHECK(value == 1.5);
|
||||||
|
CHECK(token.text == "1.5");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ using nlohmann::json;
|
|||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
#ifdef JSON_HAS_CPP_17
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// Test suite for verifying MessagePack handling with std::byte input
|
// Test suite for verifying MessagePack handling with std::byte input
|
||||||
@@ -96,4 +97,33 @@ TEST_CASE("MessagePack with std::byte")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JSON text (not MessagePack) as std::byte input: the error position is
|
||||||
|
// computed from the bytes consumed so far
|
||||||
|
TEST_CASE("JSON text with std::byte")
|
||||||
|
{
|
||||||
|
const auto to_bytes = [](const std::string & text)
|
||||||
|
{
|
||||||
|
std::vector<std::byte> bytes;
|
||||||
|
for (const char c : text)
|
||||||
|
{
|
||||||
|
bytes.push_back(static_cast<std::byte>(c));
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("valid text")
|
||||||
|
{
|
||||||
|
const json j = json::parse(to_bytes("[1,\n2]"));
|
||||||
|
CHECK(j == json({1, 2}));
|
||||||
|
CHECK(json::accept(to_bytes("[1,\n2]")));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("invalid text")
|
||||||
|
{
|
||||||
|
CHECK_FALSE(json::accept(to_bytes("[1,\n2,")));
|
||||||
|
CHECK_THROWS_WITH_AS(json::parse(to_bytes("[1,\n2,")),
|
||||||
|
"[json.exception.parse_error.101] parse error at line 2, column 3: syntax error while parsing value - unexpected end of input; expected '[', '{', or a literal", json::parse_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -16,6 +16,7 @@ using nlohmann::ordered_json;
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
TEST_CASE("ordered_json")
|
TEST_CASE("ordered_json")
|
||||||
{
|
{
|
||||||
@@ -515,3 +516,87 @@ TEST_CASE("parsing random trees of arrays and objects")
|
|||||||
CHECK(json::parse(text).dump() == expected.dump());
|
CHECK(json::parse(text).dump() == expected.dump());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("object_key_index")
|
||||||
|
{
|
||||||
|
// the hash table of the DOM parser finds the position of a key in an
|
||||||
|
// ordered_json object; the parser creates it for exactly 64 elements, but
|
||||||
|
// it is correct for any number of elements it is given
|
||||||
|
using nlohmann::detail::object_key_index;
|
||||||
|
const std::size_t not_found = object_key_index::npos;
|
||||||
|
|
||||||
|
for (const std::size_t initial :
|
||||||
|
{
|
||||||
|
std::size_t{0}, std::size_t{10}, std::size_t{64}, std::size_t{65}, std::size_t{300}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(initial)
|
||||||
|
|
||||||
|
// the hashes are not distinct on purpose: elements with the same
|
||||||
|
// hash are told apart by the caller
|
||||||
|
const auto hash_of = [](std::size_t position)
|
||||||
|
{
|
||||||
|
return (position % 50) * 0x9E3779B97F4A7C15ull;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::size_t> hashes;
|
||||||
|
for (std::size_t i = 0; i < initial; ++i)
|
||||||
|
{
|
||||||
|
hashes.push_back(hash_of(i));
|
||||||
|
}
|
||||||
|
object_key_index index(std::move(hashes));
|
||||||
|
|
||||||
|
// the elements that are there
|
||||||
|
for (std::size_t i = 0; i < initial; ++i)
|
||||||
|
{
|
||||||
|
CHECK(index.find(hash_of(i), [i](std::size_t position)
|
||||||
|
{
|
||||||
|
return position == i;
|
||||||
|
}) == i);
|
||||||
|
}
|
||||||
|
CHECK(index.find(hash_of(1), [](std::size_t /*position*/)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}) == not_found);
|
||||||
|
|
||||||
|
// elements that are added later, until the table grows
|
||||||
|
for (std::size_t i = initial; i < initial + 600; ++i)
|
||||||
|
{
|
||||||
|
index.add(hash_of(i));
|
||||||
|
CHECK(index.find(hash_of(i), [i](std::size_t position)
|
||||||
|
{
|
||||||
|
return position == i;
|
||||||
|
}) == i);
|
||||||
|
}
|
||||||
|
CHECK(index.find(hash_of(initial), [initial](std::size_t position)
|
||||||
|
{
|
||||||
|
return position == initial;
|
||||||
|
}) == initial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("JSON pointers on ordered_json")
|
||||||
|
{
|
||||||
|
using pointer = ordered_json::json_pointer;
|
||||||
|
|
||||||
|
SECTION("operator[] inserts a missing key at the end")
|
||||||
|
{
|
||||||
|
ordered_json j = {{"z", 1}, {"x", {{"y", 2}}}};
|
||||||
|
|
||||||
|
j[pointer("/a")] = 3;
|
||||||
|
j[pointer("/x/b")] = 4;
|
||||||
|
|
||||||
|
CHECK(j == ordered_json({{"z", 1}, {"x", {{"y", 2}, {"b", 4}}}, {"a", 3}}));
|
||||||
|
CHECK(j.dump() == R"({"z":1,"x":{"y":2,"b":4},"a":3})");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("unflatten of a flattened object whose keys are not sorted")
|
||||||
|
{
|
||||||
|
// the prefixes /a and /c denote arrays; a key of /a follows one of /c
|
||||||
|
const ordered_json flat = {{"/a/0/p", 1}, {"/c/0/x", 2}, {"/a/0/q", 3}, {"/a/1/p", 4}};
|
||||||
|
const ordered_json expected = {{"a", {{{"p", 1}, {"q", 3}}, {{"p", 4}}}}, {"c", {{{"x", 2}}}}};
|
||||||
|
|
||||||
|
CHECK(flat.unflatten() == expected);
|
||||||
|
CHECK(expected.flatten().unflatten() == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,11 +23,13 @@ using nlohmann::json;
|
|||||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <utility>
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
#include "test_utils.hpp"
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
@@ -1519,6 +1521,75 @@ TEST_CASE("regression tests 1")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("operator[](json_pointer) continues with the member that emplace() found")
|
||||||
|
{
|
||||||
|
// see the section above: the lookup does not find "k", but emplace()
|
||||||
|
// finds "x" and the pointer continues with the member of "x". Here
|
||||||
|
// the member is a container, so the pointer can go on below it. Only
|
||||||
|
// the outermost object may be a copy (with restarted timestamps): the
|
||||||
|
// value of "x" is moved in, so that its own lookups work.
|
||||||
|
const my_json orig = my_json::parse(R"({"x":null})");
|
||||||
|
const auto make = [&orig]()
|
||||||
|
{
|
||||||
|
my_json::object_t top = orig.get_ref<const my_json::object_t&>();
|
||||||
|
top.at("x") = my_json::parse(R"({"a":{"b":1},"n":null,"arr":[10,20],"objs":[{"m":5},{"m":6}]})");
|
||||||
|
return my_json(std::move(top));
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("existing members")
|
||||||
|
{
|
||||||
|
// emplace() registers the key "k" in the object, so that every
|
||||||
|
// pointer needs a new object to reach the member of "x" again
|
||||||
|
const std::array<std::pair<const char*, const char*>, 4> pointers = {{{"/k/a/b", "1"}, {"/k/arr/1", "20"}, {"/k/n", "null"}, {"/k/objs/1/m", "6"}}};
|
||||||
|
for (const auto& pointer : pointers)
|
||||||
|
{
|
||||||
|
CAPTURE(pointer.first)
|
||||||
|
my_json j = make();
|
||||||
|
CHECK(j[my_json::json_pointer(pointer.first)].dump() == pointer.second);
|
||||||
|
CHECK(j.size() == 1);
|
||||||
|
CHECK(j["x"].size() == 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a missing member of an object")
|
||||||
|
{
|
||||||
|
my_json j = make();
|
||||||
|
#ifdef JSON_NO_THREAD_LOCAL
|
||||||
|
// the objects below the outermost one are not copied by their
|
||||||
|
// object_t here, but member by member, which keeps their
|
||||||
|
// timestamps correct: "c" is not mistaken for "b" and is inserted
|
||||||
|
CHECK(j[my_json::json_pointer("/k/a/c/d")].is_null());
|
||||||
|
CHECK(j["x"]["a"].dump() == R"({"b":1,"c":{"d":null}})");
|
||||||
|
#else
|
||||||
|
// the timestamp of "c" is that of "b" again, so that emplace()
|
||||||
|
// finds the member "b", a number, which has no member "d"
|
||||||
|
CHECK_THROWS_AS(j[my_json::json_pointer("/k/a/c/d")], my_json::out_of_range&);
|
||||||
|
CHECK(j["x"]["a"].dump() == R"({"b":1})");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a null value is converted to an object")
|
||||||
|
{
|
||||||
|
my_json j = make();
|
||||||
|
j[my_json::json_pointer("/k/n/c")] = 3;
|
||||||
|
CHECK(j["x"]["n"].dump() == R"({"c":3})");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the end of an array")
|
||||||
|
{
|
||||||
|
my_json j = make();
|
||||||
|
j[my_json::json_pointer("/k/arr/-")] = 30;
|
||||||
|
CHECK(j["x"]["arr"].dump() == "[10,20,30]");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("an index past the end of an array")
|
||||||
|
{
|
||||||
|
my_json j = make();
|
||||||
|
j[my_json::json_pointer("/k/arr/3")] = 40;
|
||||||
|
CHECK(j["x"]["arr"].dump() == "[10,20,null,40]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("issue #977 - Assigning between different json types")
|
SECTION("issue #977 - Assigning between different json types")
|
||||||
{
|
{
|
||||||
foo_json lj = ns::foo{3};
|
foo_json lj = ns::foo{3};
|
||||||
|
|||||||
@@ -441,6 +441,10 @@ TEST_CASE("regression tests 2")
|
|||||||
|
|
||||||
float_json j2 = {1000.0, 2000.0, 3000.0};
|
float_json j2 = {1000.0, 2000.0, 3000.0};
|
||||||
CHECK(float_json::from_ubjson(float_json::to_ubjson(j2, true, true)) == j2);
|
CHECK(float_json::from_ubjson(float_json::to_ubjson(j2, true, true)) == j2);
|
||||||
|
|
||||||
|
// half-precision floats of both signs are converted to the float type
|
||||||
|
CHECK(float_json::from_cbor(std::vector<std::uint8_t> {0xF9, 0xC0, 0x00}) == -2.0f);
|
||||||
|
CHECK(float_json::from_cbor(std::vector<std::uint8_t> {0xF9, 0x3C, 0x00}) == 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("issue #1045 - Using STL algorithms with JSON containers with expected results?")
|
SECTION("issue #1045 - Using STL algorithms with JSON containers with expected results?")
|
||||||
|
|||||||
@@ -19,11 +19,13 @@
|
|||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <istream>
|
#include <istream>
|
||||||
|
#include <limits>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <streambuf>
|
#include <streambuf>
|
||||||
@@ -93,6 +95,33 @@ class chunked_streambuf : public std::streambuf
|
|||||||
std::size_t m_unbuffered;
|
std::size_t m_unbuffered;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if TEST_STREAM_BULK_SCAN
|
||||||
|
// A streambuf that claims a get area of several gigabytes, of which only the
|
||||||
|
// first bytes exist. It is only used to move the read position of the adapter,
|
||||||
|
// never to read beyond them, so that skipping more than gbump() can bump at
|
||||||
|
// once can be tested without allocating that much.
|
||||||
|
class oversized_get_area_streambuf : public std::streambuf
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit oversized_get_area_streambuf(std::size_t claimed_size)
|
||||||
|
{
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
||||||
|
setg(m_data.data(), m_data.data(), m_data.data() + claimed_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give the get area back its real size, so that the adapter may read the
|
||||||
|
// character at the read position when it is destroyed (with
|
||||||
|
// JSON_PRECISE_STREAM_POSITION, it consumes the last character returned).
|
||||||
|
void restore_get_area()
|
||||||
|
{
|
||||||
|
setg(m_data.data(), m_data.data(), m_data.data() + m_data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::array<char, 8> m_data{{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}};
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
// how a test reads from the stream
|
// how a test reads from the stream
|
||||||
enum class read_mode
|
enum class read_mode
|
||||||
{
|
{
|
||||||
@@ -433,6 +462,35 @@ TEST_CASE("parsing from a std::istream scans the get area in bulk")
|
|||||||
ia.copy_token(token, false);
|
ia.copy_token(token, false);
|
||||||
CHECK(token.empty());
|
CHECK(token.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("skipping more characters than gbump() takes at once")
|
||||||
|
{
|
||||||
|
// gbump() takes an int, but a get area can be larger
|
||||||
|
const std::size_t claimed = sizeof(std::size_t) < 8 ? 0 : std::size_t{3} << 30u;
|
||||||
|
oversized_get_area_streambuf buf(claimed);
|
||||||
|
if (claimed == 0 || static_cast<std::size_t>(buf.in_avail()) != claimed)
|
||||||
|
{
|
||||||
|
// a 32-bit size_t, or a standard library whose streambuf stores
|
||||||
|
// the size of the get area in an int (MSVC)
|
||||||
|
MESSAGE("a get area larger than an int is not supported");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::istream is(&buf);
|
||||||
|
nlohmann::detail::input_stream_adapter ia(is);
|
||||||
|
CHECK(ia.get_character() == 'a');
|
||||||
|
CHECK(ia.bulk_remaining() == claimed - 1);
|
||||||
|
|
||||||
|
const std::size_t skipped = static_cast<std::size_t>((std::numeric_limits<int>::max)()) + 10;
|
||||||
|
ia.bulk_skip(skipped);
|
||||||
|
CHECK(ia.bulk_remaining() == claimed - 1 - skipped);
|
||||||
|
|
||||||
|
ia.bulk_skip(ia.bulk_remaining());
|
||||||
|
CHECK(ia.bulk_remaining() == 0);
|
||||||
|
|
||||||
|
buf.restore_get_area();
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SECTION("SAX parsing from a stream")
|
SECTION("SAX parsing from a stream")
|
||||||
|
|||||||
@@ -928,3 +928,49 @@ TEST_CASE("short decimals")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("decimals of up to 17 digits")
|
||||||
|
{
|
||||||
|
// write_decimal() lays out digits * 10^exp as format_buffer() does; to_chars()
|
||||||
|
// calls it for the doubles whose digits write_shortest() does not convert at
|
||||||
|
// once, and the digits may be as many as 17
|
||||||
|
const auto written = [](std::uint64_t digits, int exp) -> std::string
|
||||||
|
{
|
||||||
|
std::array<char, 64> buf{}; // (up to 41 bytes are written)
|
||||||
|
char* const end = nlohmann::detail::dtoa_impl::write_decimal(buf.data(), digits, exp);
|
||||||
|
return {buf.data(), end};
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("fixed notation")
|
||||||
|
{
|
||||||
|
CHECK(written(1, 0) == "1.0");
|
||||||
|
CHECK(written(1234, -2) == "12.34");
|
||||||
|
CHECK(written(123, 2) == "12300.0");
|
||||||
|
CHECK(written(50000000000u, -1) == "5000000000.0");
|
||||||
|
CHECK(written(100000000u, -8) == "1.0");
|
||||||
|
CHECK(written(12345678u, -12) == "1.2345678e-05");
|
||||||
|
CHECK(written(1234, 11) == "123400000000000.0");
|
||||||
|
// the point moves left of the first digit
|
||||||
|
CHECK(written(5, -3) == "0.005");
|
||||||
|
CHECK(written(12345678901234567u, -20) == "0.00012345678901234567");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("exponent notation")
|
||||||
|
{
|
||||||
|
CHECK(written(5, -5) == "5e-05");
|
||||||
|
CHECK(written(1234, 12) == "1.234e+15");
|
||||||
|
CHECK(written(5, -324) == "5e-324");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("17 digits")
|
||||||
|
{
|
||||||
|
CHECK(written(12345678901234567u, -16) == "1.2345678901234567");
|
||||||
|
CHECK(written(12345678901234567u, -19) == "0.0012345678901234567");
|
||||||
|
CHECK(written(12345678901234567u, 5) == "1.2345678901234567e+21");
|
||||||
|
CHECK(written(99999999999999999u, -17) == "0.99999999999999999");
|
||||||
|
CHECK(written(10000000000000001u, -16) == "1.0000000000000001");
|
||||||
|
// trailing zeros are not written
|
||||||
|
CHECK(written(10000000000000000u, -17) == "0.1");
|
||||||
|
CHECK(written(12000000000000000u, -15) == "12.0");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1673,6 +1673,31 @@ TEST_CASE("UBJSON")
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
CHECK_THROWS_AS(scp2.start_array((std::numeric_limits<std::size_t>::max)() - 1), json::out_of_range&);
|
CHECK_THROWS_AS(scp2.start_array((std::numeric_limits<std::size_t>::max)() - 1), json::out_of_range&);
|
||||||
|
|
||||||
|
// without exceptions, the error is reported to the caller
|
||||||
|
json l;
|
||||||
|
nlohmann::detail::json_sax_dom_callback_parser<json, decltype(nlohmann::detail::input_adapter(v_ubjson))> scp3(l, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}, false);
|
||||||
|
CHECK_FALSE(scp3.start_array((std::numeric_limits<std::size_t>::max)() - 1));
|
||||||
|
CHECK(scp3.is_errored());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("array with a known size, read without exceptions")
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> const v_ubjson = {'[', '#', 'i', 2, 'i', 1, 'i', 2};
|
||||||
|
json j;
|
||||||
|
nlohmann::detail::json_sax_dom_parser<json, decltype(nlohmann::detail::input_adapter(v_ubjson))> sax(j, false);
|
||||||
|
CHECK(json::sax_parse(v_ubjson, &sax, json::input_format_t::ubjson));
|
||||||
|
CHECK(j == json({1, 2}));
|
||||||
|
|
||||||
|
// the readers reject a size this large before they announce
|
||||||
|
// it, so it can only reach start_array() directly
|
||||||
|
json k;
|
||||||
|
nlohmann::detail::json_sax_dom_parser<json, decltype(nlohmann::detail::input_adapter(v_ubjson))> sax2(k, false);
|
||||||
|
CHECK_FALSE(sax2.start_array((std::numeric_limits<std::size_t>::max)() - 1));
|
||||||
|
CHECK(sax2.is_errored());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user