Compare commits

..
Author SHA1 Message Date
Claude 315a1daada Expect the byte range of the root in the json_view boolean get<T>() test
With JSON_DIAGNOSTIC_POSITIONS, the type_error.302 message starts with
"(bytes 0-4)"; build the expected text with json_view_test::message(),
as the other tests of the root value do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp
Signed-off-by: Claude <noreply@anthropic.com>
2026-11-02 10:55:25 +00:00
Claude c3a69837de tests: skip throwing json_view bool get<T>() combinations without exceptions (#5988)
check_boolean_get_parity() called get<T>() for number_integer_t,
number_unsigned_t and number_float_t in its JSON_NOEXCEPTION branch, but a
boolean throws type_error.302 for exactly these types, so JSON_THROW aborted
test-json_view_cpp11/cpp17 in ci_test_noexceptions. Skip these combinations
there; the throwing checks stay in the exceptions-enabled build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp
Signed-off-by: Claude <noreply@anthropic.com>
2026-11-02 05:45:17 +00:00
Niels Lohmann 57428501b5 json_view: get<T>() of a boolean throws for number_integer_t, number_unsigned_t and number_float_t like basic_json (#5988)
basic_json::get<T>() of a boolean throws type_error.302 for exactly
number_integer_t, number_unsigned_t and number_float_t (dedicated from_json()
overloads) and gives 0/1 for every other arithmetic type. The view returned
0/1 for all of them, contradicting its documentation. The view now throws
the same exception (same message, with path/bytes decoration) for these three
types. basic_json is unchanged; its behavior is documented and pinned by a test.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-11-01 21:09:01 +01:00
24 changed files with 344 additions and 379 deletions

No files matched your search

+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -8,7 +8,7 @@ permissions:
jobs: jobs:
stale: stale:
runs-on: ubuntu-26.04 runs-on: ubuntu-latest
permissions: permissions:
issues: write issues: write
+13 -13
View File
@@ -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
+10
View File
@@ -120,6 +120,16 @@ overload (3).
See [Number conversion](../../features/types/number_handling.md#number-conversion) See [Number conversion](../../features/types/number_handling.md#number-conversion)
for more information. for more information.
!!! note "Booleans as numbers"
A JSON boolean converts to `#!cpp 0` or `#!cpp 1` for every arithmetic type **except** [`number_integer_t`](number_integer_t.md),
[`number_unsigned_t`](number_unsigned_t.md) and [`number_float_t`](number_float_t.md) (by default `#!cpp std::int64_t`,
`#!cpp std::uint64_t`, and `#!cpp double`), for which `#!cpp get<T>()` throws
[`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) ("type must be number, but is boolean"). So
`#!cpp json(true).get<int>()` is `#!cpp 1`, while `#!cpp json(true).get<std::int64_t>()` and
`#!cpp json(true).get<double>()` throw. This is the behavior of all 3.x versions;
[`basic_json_view::get<T>()`](../basic_json_view/get.md) behaves the same.
!!! note "`std::optional` conversions" !!! note "`std::optional` conversions"
Prior to version 3.13.0, `#!cpp get<std::optional<T>>()` (and other conversions to `std::optional<T>`) failed to Prior to version 3.13.0, `#!cpp get<std::optional<T>>()` (and other conversions to `std::optional<T>`) failed to
+2 -1
View File
@@ -11,7 +11,8 @@ For the types below, the conversion works directly on the flat index — no `Bas
- `#!cpp bool` - `#!cpp bool`
- arithmetic types other than `#!cpp bool` (for a boolean, `#!cpp 0`/`#!cpp 1`, exactly as for - arithmetic types other than `#!cpp bool` (for a boolean, `#!cpp 0`/`#!cpp 1`, exactly as for
[`BasicJsonType::get<T>()`](../basic_json/get.md)) [`BasicJsonType::get<T>()`](../basic_json/get.md#notes), except for `number_integer_t`, `number_unsigned_t` and
`number_float_t`, which throw `type_error.302` for a boolean, as `BasicJsonType::get<T>()` does)
- `#!cpp std::nullptr_t` - `#!cpp std::nullptr_t`
- `#!cpp std::basic_string<char, Traits, Alloc>` (including `string_t`) — a copy of the string - `#!cpp std::basic_string<char, Traits, Alloc>` (including `string_t`) — a copy of the string
- [`string_view_t`](index.md#member-types) — **no copy**: the returned view points into the document's - [`string_view_t`](index.md#member-types) — **no copy**: the returned view points into the document's
@@ -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
{ {
+28 -3
View File
@@ -33,12 +33,37 @@ namespace view
template<typename T> template<typename T>
struct value_tag {}; struct value_tag {};
/// whether basic_json reads T with a dedicated from_json() overload that does
/// not accept a boolean: exactly number_integer_t, number_unsigned_t and
/// number_float_t (the other arithmetic types convert a boolean to 0 or 1)
template<typename T, typename BasicJsonType>
struct rejects_boolean : std::integral_constant < bool,
std::is_same<T, typename BasicJsonType::number_integer_t>::value
|| std::is_same<T, typename BasicJsonType::number_unsigned_t>::value
|| std::is_same<T, typename BasicJsonType::number_float_t>::value >
{};
/// a boolean as T: 0 or 1
template<typename T>
NLOHMANN_VIEW_ALWAYS_INLINE T boolean_as(const document_data& /*d*/, const node& n, std::false_type /*rejects*/)
{
return static_cast<T>((n.flags & node_flags::is_true) != 0);
}
/// a boolean as number_integer_t, number_unsigned_t or number_float_t: type_error.302, as in basic_json
template<typename T>
[[noreturn]] NLOHMANN_VIEW_ALWAYS_INLINE T boolean_as(const document_data& d, const node& n, std::true_type /*rejects*/)
{
throw_type_error(error_site(&d, &n), exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
}
/*! /*!
@brief the number or boolean of a node converted to an arithmetic type @brief the number or boolean of a node converted to an arithmetic type
As basic_json's get<T>() for arithmetic types: integers and floats are As basic_json's get<T>() for arithmetic types: integers and floats are
converted with static_cast, booleans give 0 or 1, and other types throw converted with static_cast, booleans give 0 or 1 (but throw type_error.302 for
type_error.302. number_integer_t, number_unsigned_t and number_float_t, as basic_json does),
and other types throw type_error.302.
*/ */
template<typename T, typename BasicJsonType> template<typename T, typename BasicJsonType>
NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const node& n) NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const node& n)
@@ -52,7 +77,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
case value_t::number_float: case value_t::number_float:
return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d, n)); return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d, n));
case value_t::boolean: case value_t::boolean:
return static_cast<T>((n.flags & node_flags::is_true) != 0); return boolean_as<T>(d, n, rejects_boolean<T, BasicJsonType> {});
case value_t::null: case value_t::null:
case value_t::object: case value_t::object:
case value_t::array: case value_t::array:
+63 -67
View File
@@ -543,10 +543,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
//////////////////////// ////////////////////////
JSON_PRIVATE_UNLESS_TESTED: JSON_PRIVATE_UNLESS_TESTED:
// the type and value of a basic_json without its base class (defined
// below); the destruction helpers of json_value use it as a stand-in
struct data;
/*! /*!
@brief a JSON value @brief a JSON value
@@ -723,28 +719,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
explicit json_value(array_t* value) noexcept : array(value) {} explicit json_value(array_t* value) noexcept : array(value) {}
private: private:
// raw, allocation-free transfer of the type and value from src to // raw, allocation-free transfer of m_data from src to dst: no
// dst: no set_parents()/assert_invariant() (the former is // set_parents()/assert_invariant() (the former is O(#children) per
// O(#children) per call under JSON_DIAGNOSTICS, which would make the // call under JSON_DIAGNOSTICS, which would make the walk below
// walk below quadratic); dst takes ownership, src is left as // quadratic); dst takes ownership, src is left as value_t::null.
// value_t::null. static void take(basic_json& dst, basic_json& src) noexcept
static void take(data& dst, data& src) noexcept
{ {
dst.m_type = src.m_type; dst.m_data.m_type = src.m_data.m_type;
dst.m_value = src.m_value; dst.m_data.m_value = src.m_data.m_value;
src.m_type = value_t::null; src.m_data.m_type = value_t::null;
} }
// true if v is not an array/object, or is an already-empty one // true if v is not an array/object, or is an already-empty one
static bool has_no_children(const data& v) noexcept static bool has_no_children(const basic_json& v) noexcept
{ {
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
return v.m_value.array->empty(); return v.m_data.m_value.array->empty();
} }
if (v.m_type == value_t::object) if (v.m_data.m_type == value_t::object)
{ {
return v.m_value.object->empty(); return v.m_data.m_value.object->empty();
} }
return true; return true;
} }
@@ -753,21 +748,21 @@ private:
// its own, i.e. v can be freed in one go (its destructor then // its own, i.e. v can be freed in one go (its destructor then
// destroys scalars and empty containers only, so it recurses at most // destroys scalars and empty containers only, so it recurses at most
// one level deep); stops at the first structured child it meets // one level deep); stops at the first structured child it meets
static bool all_children_leaves(const data& v) noexcept static bool all_children_leaves(const basic_json& v) noexcept
{ {
// array_t only needs forward iterators (see custom_array_type) // array_t only needs forward iterators (see custom_array_type)
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
const auto& a = *v.m_value.array; const auto& a = *v.m_data.m_value.array;
return std::all_of(a.begin(), a.end(), [](const basic_json & el) return std::all_of(a.begin(), a.end(), [](const basic_json & el)
{ {
return has_no_children(el.m_data); return has_no_children(el);
}); });
} }
const auto& o = *v.m_value.object; const auto& o = *v.m_data.m_value.object;
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el) return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
{ {
return has_no_children(el.second.m_data); return has_no_children(el.second);
}); });
} }
@@ -799,30 +794,30 @@ private:
// the child of a non-empty array/object v that the walk in // the child of a non-empty array/object v that the walk in
// destroy_container() continues with (see walk_child_it() above) // destroy_container() continues with (see walk_child_it() above)
static data& walk_child(data& v) static basic_json& walk_child(basic_json& v)
{ {
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
return v.m_value.array->back().m_data; return v.m_data.m_value.array->back();
} }
JSON_ASSERT(v.m_type == value_t::object); JSON_ASSERT(v.m_data.m_type == value_t::object);
return walk_child_it(*v.m_value.object)->second.m_data; return walk_child_it(*v.m_data.m_value.object)->second;
} }
// removes walk_child(v) from a non-empty array/object v; this never // removes walk_child(v) from a non-empty array/object v; this never
// allocates, and since it is only ever called when that child is a // allocates, and since it is only ever called when that child is a
// scalar or an already-empty array/object, destroying it never // scalar or an already-empty array/object, destroying it never
// recurses more than one level deep (see destroy() below) // recurses more than one level deep (see destroy() below)
static void pop_walk_child(data& v) static void pop_walk_child(basic_json& v)
{ {
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
v.m_value.array->pop_back(); v.m_data.m_value.array->pop_back();
} }
else else
{ {
JSON_ASSERT(v.m_type == value_t::object); JSON_ASSERT(v.m_data.m_type == value_t::object);
v.m_value.object->erase(walk_child_it(*v.m_value.object)); v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
} }
} }
@@ -831,23 +826,23 @@ private:
// recurses at most one level deep); this is the same allocator-based // recurses at most one level deep); this is the same allocator-based
// free the old recursive implementation used, just factored out so // free the old recursive implementation used, just factored out so
// every level of the walk in destroy() can share it // every level of the walk in destroy() can share it
static void free_container(data& v) noexcept static void free_container(basic_json& v) noexcept
{ {
JSON_ASSERT(has_no_children(v) || all_children_leaves(v)); JSON_ASSERT(has_no_children(v) || all_children_leaves(v));
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
AllocatorType<array_t> alloc; AllocatorType<array_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.array); std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.array, 1); std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
} }
else else
{ {
JSON_ASSERT(v.m_type == value_t::object); JSON_ASSERT(v.m_data.m_type == value_t::object);
AllocatorType<object_t> alloc; AllocatorType<object_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.object); std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.object, 1); std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
} }
v.m_type = value_t::null; // avoid a double free if v is later destructed v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
} }
public: public:
@@ -878,16 +873,14 @@ public:
// This json_value is not itself a basic_json, so the top-level // This json_value is not itself a basic_json, so the top-level
// container is first moved into a local stand-in ("cur"); a // container is first moved into a local stand-in ("cur"); a
// default-constructed data has a null pointer in its m_value // default-constructed basic_json has a null pointer in its
// (see data::m_value's initializer), so swapping it with *this // m_value (see data::m_value's initializer), so swapping it
// leaves this union's own pointer null, and it is never looked // with *this leaves this union's own pointer null, and it is
// at or freed a second time. The stand-ins are data, not // never looked at or freed a second time.
// basic_json, so that destroying a value never constructs a basic_json cur;
// CustomBaseClass, whose constructor may allocate (#5995). cur.m_data.m_type = t;
data cur;
cur.m_type = t;
using std::swap; using std::swap;
swap(cur.m_value, *this); swap(cur.m_data.m_value, *this);
destroy_bounded(cur, 0); destroy_bounded(cur, 0);
} }
@@ -896,7 +889,7 @@ public:
// The first recursion_depth_limit() levels are destroyed by plain // The first recursion_depth_limit() levels are destroyed by plain
// recursion, which is the fastest way and cannot throw; anything // recursion, which is the fastest way and cannot throw; anything
// nested deeper is handed to destroy_walk(), which uses no call stack. // nested deeper is handed to destroy_walk(), which uses no call stack.
static void destroy_bounded(data& v, std::size_t depth) noexcept static void destroy_bounded(basic_json& v, std::size_t depth) noexcept
{ {
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit())) if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
{ {
@@ -904,23 +897,23 @@ public:
return; return;
} }
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
for (auto& child : *v.m_value.array) for (auto& child : *v.m_data.m_value.array)
{ {
if (child.m_data.m_type == value_t::array || child.m_data.m_type == value_t::object) if (child.m_data.m_type == value_t::array || child.m_data.m_type == value_t::object)
{ {
destroy_bounded(child.m_data, depth + 1); destroy_bounded(child, depth + 1);
} }
} }
} }
else else
{ {
for (auto& child : *v.m_value.object) for (auto& child : *v.m_data.m_value.object)
{ {
if (child.second.m_data.m_type == value_t::array || child.second.m_data.m_type == value_t::object) if (child.second.m_data.m_type == value_t::array || child.second.m_data.m_type == value_t::object)
{ {
destroy_bounded(child.second.m_data, depth + 1); destroy_bounded(child.second, depth + 1);
} }
} }
} }
@@ -947,15 +940,18 @@ public:
// array/object, which neither allocates nor recurses more // array/object, which neither allocates nor recurses more
// than one level deep. // than one level deep.
// //
// Like destroy_container(), the walk keeps the containers it // This json_value is not itself a basic_json, so the
// holds in data stand-ins (cur, prev, gp, tmp), which never // top-level container is first moved into a local stand-in
// construct a CustomBaseClass. // ("cur"); a default-constructed basic_json has a null
static void destroy_walk(data& top) noexcept // pointer in its m_value (see data::m_value's initializer),
// so swapping it with *this leaves this union's own pointer
// null, and it is never looked at or freed a second time.
static void destroy_walk(basic_json& top) noexcept
{ {
data cur; basic_json cur;
take(cur, top); take(cur, top);
data prev; // value_t::null: no parent basic_json prev; // value_t::null: no parent
// true right after cur was entered: its children have not been // true right after cur was entered: its children have not been
// looked at yet. If none of them has children of its own, cur is // looked at yet. If none of them has children of its own, cur is
@@ -969,7 +965,7 @@ public:
fresh = false; fresh = false;
if (has_no_children(cur) || (fresh_now && all_children_leaves(cur))) if (has_no_children(cur) || (fresh_now && all_children_leaves(cur)))
{ {
if (prev.m_type == value_t::null) if (prev.m_data.m_type == value_t::null)
{ {
free_container(cur); free_container(cur);
return; // back at the top with nothing left to do return; // back at the top with nothing left to do
@@ -978,7 +974,7 @@ public:
// ascend: detach the grandparent link from prev's // ascend: detach the grandparent link from prev's
// walk_child() slot, drop that (now null) slot, free cur // walk_child() slot, drop that (now null) slot, free cur
// (it is empty), then move up one level // (it is empty), then move up one level
data gp; basic_json gp;
take(gp, walk_child(prev)); take(gp, walk_child(prev));
pop_walk_child(prev); pop_walk_child(prev);
@@ -989,7 +985,7 @@ public:
continue; continue;
} }
data& cur_child_ref = walk_child(cur); basic_json& cur_child_ref = walk_child(cur);
if (has_no_children(cur_child_ref)) if (has_no_children(cur_child_ref))
{ {
@@ -1001,7 +997,7 @@ public:
// descend into the non-empty child, reversing the // descend into the non-empty child, reversing the
// link: its slot takes over prev, and the child becomes // link: its slot takes over prev, and the child becomes
// the new cur // the new cur
data tmp; basic_json tmp;
take(tmp, cur_child_ref); take(tmp, cur_child_ref);
take(cur_child_ref, prev); take(cur_child_ref, prev);
take(prev, cur); take(prev, cur);
+67 -109
View File
@@ -18474,13 +18474,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;
} }
@@ -18504,7 +18502,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.
*/ */
@@ -18563,42 +18561,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
{ {
@@ -38373,10 +38335,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
//////////////////////// ////////////////////////
JSON_PRIVATE_UNLESS_TESTED: JSON_PRIVATE_UNLESS_TESTED:
// the type and value of a basic_json without its base class (defined
// below); the destruction helpers of json_value use it as a stand-in
struct data;
/*! /*!
@brief a JSON value @brief a JSON value
@@ -38553,28 +38511,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
explicit json_value(array_t* value) noexcept : array(value) {} explicit json_value(array_t* value) noexcept : array(value) {}
private: private:
// raw, allocation-free transfer of the type and value from src to // raw, allocation-free transfer of m_data from src to dst: no
// dst: no set_parents()/assert_invariant() (the former is // set_parents()/assert_invariant() (the former is O(#children) per
// O(#children) per call under JSON_DIAGNOSTICS, which would make the // call under JSON_DIAGNOSTICS, which would make the walk below
// walk below quadratic); dst takes ownership, src is left as // quadratic); dst takes ownership, src is left as value_t::null.
// value_t::null. static void take(basic_json& dst, basic_json& src) noexcept
static void take(data& dst, data& src) noexcept
{ {
dst.m_type = src.m_type; dst.m_data.m_type = src.m_data.m_type;
dst.m_value = src.m_value; dst.m_data.m_value = src.m_data.m_value;
src.m_type = value_t::null; src.m_data.m_type = value_t::null;
} }
// true if v is not an array/object, or is an already-empty one // true if v is not an array/object, or is an already-empty one
static bool has_no_children(const data& v) noexcept static bool has_no_children(const basic_json& v) noexcept
{ {
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
return v.m_value.array->empty(); return v.m_data.m_value.array->empty();
} }
if (v.m_type == value_t::object) if (v.m_data.m_type == value_t::object)
{ {
return v.m_value.object->empty(); return v.m_data.m_value.object->empty();
} }
return true; return true;
} }
@@ -38583,21 +38540,21 @@ private:
// its own, i.e. v can be freed in one go (its destructor then // its own, i.e. v can be freed in one go (its destructor then
// destroys scalars and empty containers only, so it recurses at most // destroys scalars and empty containers only, so it recurses at most
// one level deep); stops at the first structured child it meets // one level deep); stops at the first structured child it meets
static bool all_children_leaves(const data& v) noexcept static bool all_children_leaves(const basic_json& v) noexcept
{ {
// array_t only needs forward iterators (see custom_array_type) // array_t only needs forward iterators (see custom_array_type)
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
const auto& a = *v.m_value.array; const auto& a = *v.m_data.m_value.array;
return std::all_of(a.begin(), a.end(), [](const basic_json & el) return std::all_of(a.begin(), a.end(), [](const basic_json & el)
{ {
return has_no_children(el.m_data); return has_no_children(el);
}); });
} }
const auto& o = *v.m_value.object; const auto& o = *v.m_data.m_value.object;
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el) return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
{ {
return has_no_children(el.second.m_data); return has_no_children(el.second);
}); });
} }
@@ -38629,30 +38586,30 @@ private:
// the child of a non-empty array/object v that the walk in // the child of a non-empty array/object v that the walk in
// destroy_container() continues with (see walk_child_it() above) // destroy_container() continues with (see walk_child_it() above)
static data& walk_child(data& v) static basic_json& walk_child(basic_json& v)
{ {
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
return v.m_value.array->back().m_data; return v.m_data.m_value.array->back();
} }
JSON_ASSERT(v.m_type == value_t::object); JSON_ASSERT(v.m_data.m_type == value_t::object);
return walk_child_it(*v.m_value.object)->second.m_data; return walk_child_it(*v.m_data.m_value.object)->second;
} }
// removes walk_child(v) from a non-empty array/object v; this never // removes walk_child(v) from a non-empty array/object v; this never
// allocates, and since it is only ever called when that child is a // allocates, and since it is only ever called when that child is a
// scalar or an already-empty array/object, destroying it never // scalar or an already-empty array/object, destroying it never
// recurses more than one level deep (see destroy() below) // recurses more than one level deep (see destroy() below)
static void pop_walk_child(data& v) static void pop_walk_child(basic_json& v)
{ {
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
v.m_value.array->pop_back(); v.m_data.m_value.array->pop_back();
} }
else else
{ {
JSON_ASSERT(v.m_type == value_t::object); JSON_ASSERT(v.m_data.m_type == value_t::object);
v.m_value.object->erase(walk_child_it(*v.m_value.object)); v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
} }
} }
@@ -38661,23 +38618,23 @@ private:
// recurses at most one level deep); this is the same allocator-based // recurses at most one level deep); this is the same allocator-based
// free the old recursive implementation used, just factored out so // free the old recursive implementation used, just factored out so
// every level of the walk in destroy() can share it // every level of the walk in destroy() can share it
static void free_container(data& v) noexcept static void free_container(basic_json& v) noexcept
{ {
JSON_ASSERT(has_no_children(v) || all_children_leaves(v)); JSON_ASSERT(has_no_children(v) || all_children_leaves(v));
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
AllocatorType<array_t> alloc; AllocatorType<array_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.array); std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.array, 1); std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
} }
else else
{ {
JSON_ASSERT(v.m_type == value_t::object); JSON_ASSERT(v.m_data.m_type == value_t::object);
AllocatorType<object_t> alloc; AllocatorType<object_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.object); std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.object, 1); std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
} }
v.m_type = value_t::null; // avoid a double free if v is later destructed v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
} }
public: public:
@@ -38708,16 +38665,14 @@ public:
// This json_value is not itself a basic_json, so the top-level // This json_value is not itself a basic_json, so the top-level
// container is first moved into a local stand-in ("cur"); a // container is first moved into a local stand-in ("cur"); a
// default-constructed data has a null pointer in its m_value // default-constructed basic_json has a null pointer in its
// (see data::m_value's initializer), so swapping it with *this // m_value (see data::m_value's initializer), so swapping it
// leaves this union's own pointer null, and it is never looked // with *this leaves this union's own pointer null, and it is
// at or freed a second time. The stand-ins are data, not // never looked at or freed a second time.
// basic_json, so that destroying a value never constructs a basic_json cur;
// CustomBaseClass, whose constructor may allocate (#5995). cur.m_data.m_type = t;
data cur;
cur.m_type = t;
using std::swap; using std::swap;
swap(cur.m_value, *this); swap(cur.m_data.m_value, *this);
destroy_bounded(cur, 0); destroy_bounded(cur, 0);
} }
@@ -38726,7 +38681,7 @@ public:
// The first recursion_depth_limit() levels are destroyed by plain // The first recursion_depth_limit() levels are destroyed by plain
// recursion, which is the fastest way and cannot throw; anything // recursion, which is the fastest way and cannot throw; anything
// nested deeper is handed to destroy_walk(), which uses no call stack. // nested deeper is handed to destroy_walk(), which uses no call stack.
static void destroy_bounded(data& v, std::size_t depth) noexcept static void destroy_bounded(basic_json& v, std::size_t depth) noexcept
{ {
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit())) if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
{ {
@@ -38734,23 +38689,23 @@ public:
return; return;
} }
if (v.m_type == value_t::array) if (v.m_data.m_type == value_t::array)
{ {
for (auto& child : *v.m_value.array) for (auto& child : *v.m_data.m_value.array)
{ {
if (child.m_data.m_type == value_t::array || child.m_data.m_type == value_t::object) if (child.m_data.m_type == value_t::array || child.m_data.m_type == value_t::object)
{ {
destroy_bounded(child.m_data, depth + 1); destroy_bounded(child, depth + 1);
} }
} }
} }
else else
{ {
for (auto& child : *v.m_value.object) for (auto& child : *v.m_data.m_value.object)
{ {
if (child.second.m_data.m_type == value_t::array || child.second.m_data.m_type == value_t::object) if (child.second.m_data.m_type == value_t::array || child.second.m_data.m_type == value_t::object)
{ {
destroy_bounded(child.second.m_data, depth + 1); destroy_bounded(child.second, depth + 1);
} }
} }
} }
@@ -38777,15 +38732,18 @@ public:
// array/object, which neither allocates nor recurses more // array/object, which neither allocates nor recurses more
// than one level deep. // than one level deep.
// //
// Like destroy_container(), the walk keeps the containers it // This json_value is not itself a basic_json, so the
// holds in data stand-ins (cur, prev, gp, tmp), which never // top-level container is first moved into a local stand-in
// construct a CustomBaseClass. // ("cur"); a default-constructed basic_json has a null
static void destroy_walk(data& top) noexcept // pointer in its m_value (see data::m_value's initializer),
// so swapping it with *this leaves this union's own pointer
// null, and it is never looked at or freed a second time.
static void destroy_walk(basic_json& top) noexcept
{ {
data cur; basic_json cur;
take(cur, top); take(cur, top);
data prev; // value_t::null: no parent basic_json prev; // value_t::null: no parent
// true right after cur was entered: its children have not been // true right after cur was entered: its children have not been
// looked at yet. If none of them has children of its own, cur is // looked at yet. If none of them has children of its own, cur is
@@ -38799,7 +38757,7 @@ public:
fresh = false; fresh = false;
if (has_no_children(cur) || (fresh_now && all_children_leaves(cur))) if (has_no_children(cur) || (fresh_now && all_children_leaves(cur)))
{ {
if (prev.m_type == value_t::null) if (prev.m_data.m_type == value_t::null)
{ {
free_container(cur); free_container(cur);
return; // back at the top with nothing left to do return; // back at the top with nothing left to do
@@ -38808,7 +38766,7 @@ public:
// ascend: detach the grandparent link from prev's // ascend: detach the grandparent link from prev's
// walk_child() slot, drop that (now null) slot, free cur // walk_child() slot, drop that (now null) slot, free cur
// (it is empty), then move up one level // (it is empty), then move up one level
data gp; basic_json gp;
take(gp, walk_child(prev)); take(gp, walk_child(prev));
pop_walk_child(prev); pop_walk_child(prev);
@@ -38819,7 +38777,7 @@ public:
continue; continue;
} }
data& cur_child_ref = walk_child(cur); basic_json& cur_child_ref = walk_child(cur);
if (has_no_children(cur_child_ref)) if (has_no_children(cur_child_ref))
{ {
@@ -38831,7 +38789,7 @@ public:
// descend into the non-empty child, reversing the // descend into the non-empty child, reversing the
// link: its slot takes over prev, and the child becomes // link: its slot takes over prev, and the child becomes
// the new cur // the new cur
data tmp; basic_json tmp;
take(tmp, cur_child_ref); take(tmp, cur_child_ref);
take(cur_child_ref, prev); take(cur_child_ref, prev);
take(prev, cur); take(prev, cur);
+28 -3
View File
@@ -10067,12 +10067,37 @@ namespace view
template<typename T> template<typename T>
struct value_tag {}; struct value_tag {};
/// whether basic_json reads T with a dedicated from_json() overload that does
/// not accept a boolean: exactly number_integer_t, number_unsigned_t and
/// number_float_t (the other arithmetic types convert a boolean to 0 or 1)
template<typename T, typename BasicJsonType>
struct rejects_boolean : std::integral_constant < bool,
std::is_same<T, typename BasicJsonType::number_integer_t>::value
|| std::is_same<T, typename BasicJsonType::number_unsigned_t>::value
|| std::is_same<T, typename BasicJsonType::number_float_t>::value >
{};
/// a boolean as T: 0 or 1
template<typename T>
NLOHMANN_VIEW_ALWAYS_INLINE T boolean_as(const document_data& /*d*/, const node& n, std::false_type /*rejects*/)
{
return static_cast<T>((n.flags & node_flags::is_true) != 0);
}
/// a boolean as number_integer_t, number_unsigned_t or number_float_t: type_error.302, as in basic_json
template<typename T>
[[noreturn]] NLOHMANN_VIEW_ALWAYS_INLINE T boolean_as(const document_data& d, const node& n, std::true_type /*rejects*/)
{
throw_type_error(error_site(&d, &n), exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
}
/*! /*!
@brief the number or boolean of a node converted to an arithmetic type @brief the number or boolean of a node converted to an arithmetic type
As basic_json's get<T>() for arithmetic types: integers and floats are As basic_json's get<T>() for arithmetic types: integers and floats are
converted with static_cast, booleans give 0 or 1, and other types throw converted with static_cast, booleans give 0 or 1 (but throw type_error.302 for
type_error.302. number_integer_t, number_unsigned_t and number_float_t, as basic_json does),
and other types throw type_error.302.
*/ */
template<typename T, typename BasicJsonType> template<typename T, typename BasicJsonType>
NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const node& n) NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const node& n)
@@ -10086,7 +10111,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
case value_t::number_float: case value_t::number_float:
return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d, n)); return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d, n));
case value_t::boolean: case value_t::boolean:
return static_cast<T>((n.flags & node_flags::is_true) != 0); return boolean_as<T>(d, n, rejects_boolean<T, BasicJsonType> {});
case value_t::null: case value_t::null:
case value_t::object: case value_t::object:
case value_t::array: case value_t::array:
-38
View File
@@ -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"
@@ -1200,31 +1199,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 +1227,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
+38
View File
@@ -1260,6 +1260,44 @@ TEST_CASE("value conversion")
#undef JSON_HAS_CPP_17 #undef JSON_HAS_CPP_17
#endif #endif
TEST_CASE("get<T>() of a boolean (#5988)")
{
// a boolean converts to 0/1 for every arithmetic type except number_integer_t, number_unsigned_t and
// number_float_t, whose dedicated from_json() overloads throw
const json t = true;
const json f = false;
SECTION("0 and 1")
{
// long and unsigned long are left out: they are std::int64_t and std::uint64_t on some platforms
CHECK(t.get<bool>() == true);
CHECK(f.get<bool>() == false);
CHECK(t.get<char>() == 1);
CHECK(t.get<signed char>() == 1);
CHECK(t.get<unsigned char>() == 1);
CHECK(t.get<short>() == 1); // NOLINT(google-runtime-int)
CHECK(t.get<unsigned short>() == 1); // NOLINT(google-runtime-int)
CHECK(t.get<int>() == 1);
CHECK(t.get<unsigned>() == 1);
CHECK(t.get<float>() == 1.0F);
CHECK(t.get<long double>() == 1.0L);
CHECK(f.get<int>() == 0);
CHECK(f.get<float>() == 0.0F);
}
#if !defined(JSON_NOEXCEPTION)
SECTION("number_integer_t, number_unsigned_t and number_float_t throw")
{
CHECK_THROWS_WITH_AS(t.get<json::number_integer_t>(), "[json.exception.type_error.302] type must be number, but is boolean", json::type_error&);
CHECK_THROWS_WITH_AS(t.get<json::number_unsigned_t>(), "[json.exception.type_error.302] type must be number, but is boolean", json::type_error&);
CHECK_THROWS_WITH_AS(t.get<json::number_float_t>(), "[json.exception.type_error.302] type must be number, but is boolean", json::type_error&);
CHECK_THROWS_WITH_AS(f.get<json::number_integer_t>(), "[json.exception.type_error.302] type must be number, but is boolean", json::type_error&);
CHECK_THROWS_WITH_AS(f.get<json::number_unsigned_t>(), "[json.exception.type_error.302] type must be number, but is boolean", json::type_error&);
CHECK_THROWS_WITH_AS(f.get<json::number_float_t>(), "[json.exception.type_error.302] type must be number, but is boolean", json::type_error&);
}
#endif
}
#ifdef JSON_HAS_CPP_14 #ifdef JSON_HAS_CPP_14
#undef JSON_HAS_CPP_14 #undef JSON_HAS_CPP_14
#endif #endif
-90
View File
@@ -7,7 +7,6 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
#include <algorithm> #include <algorithm>
#include <memory>
#include <set> #include <set>
#include <sstream> #include <sstream>
#include <string> #include <string>
@@ -496,92 +495,3 @@ TEST_CASE("Regression test for issue #5674 - copy construction must not require
CHECK(*c == 1); CHECK(*c == 1);
} }
} }
// counts the constructions and destructions of the base class (#5995)
struct counting_base
{
static std::size_t constructions;
static std::size_t destructions;
counting_base() noexcept
{
++constructions;
}
counting_base(const counting_base& /*unused*/) noexcept
{
++constructions;
}
counting_base(counting_base&& /*unused*/) noexcept
{
++constructions;
}
counting_base& operator=(const counting_base& /*unused*/) noexcept = default;
counting_base& operator=(counting_base&& /*unused*/) noexcept = default;
~counting_base()
{
++destructions;
}
static void reset() noexcept
{
constructions = 0;
destructions = 0;
}
};
std::size_t counting_base::constructions = 0;
std::size_t counting_base::destructions = 0;
using json_with_counting_base = nlohmann::json::with_base_class_t<counting_base>;
TEST_CASE("destroying a value does not construct the custom base class")
{
// regression test for #5995: destroying an array or object used a
// default-constructed basic_json as a stand-in, which constructed a base
// class that belonged to no value (and could allocate in the noexcept
// destructor); now every destroyed value destroys exactly its own base
// object, and none is constructed
const auto check_destroy = [](std::unique_ptr<json_with_counting_base> j, std::size_t values)
{
counting_base::reset();
j = nullptr;
CHECK(counting_base::constructions == 0);
CHECK(counting_base::destructions == values);
};
SECTION("scalar")
{
check_destroy(std::unique_ptr<json_with_counting_base>(new json_with_counting_base(42)), 1);
}
SECTION("array and object")
{
check_destroy(std::unique_ptr<json_with_counting_base>(new json_with_counting_base(json_with_counting_base::parse(R"({"a":[1,2,3],"b":{"c":true}})"))), 7);
check_destroy(std::unique_ptr<json_with_counting_base>(new json_with_counting_base(json_with_counting_base::array())), 1);
}
SECTION("nested deeper than the recursion depth limit")
{
// alternating arrays and objects, so both kinds of container are
// walked by destroy_walk()
constexpr std::size_t depth = 1000;
std::unique_ptr<json_with_counting_base> j(new json_with_counting_base(json_with_counting_base::array({1, 2})));
for (std::size_t i = 0; i < depth; ++i)
{
json_with_counting_base outer = (i % 2 == 0) ? json_with_counting_base::object() : json_with_counting_base::array();
if (outer.is_object())
{
outer["x"] = std::move(*j);
outer["y"] = nullptr;
}
else
{
outer.push_back(std::move(*j));
outer.push_back(true);
}
*j = std::move(outer);
}
// the innermost array with its two elements, and two values per level
check_destroy(std::move(j), 3 + (2 * depth));
}
}
+79
View File
@@ -1551,6 +1551,85 @@ TEST_CASE("json_view values")
} }
} }
namespace
{
// get<T>() of a boolean: the same value or the same exception in basic_json and in the view (#5988)
template<typename T>
void check_boolean_get_parity()
{
for (const bool b :
{
true, false
})
{
const std::string text = b ? "true" : "false";
const json j = json::parse(text);
const json_document d = json_document::parse(text);
CAPTURE(text)
#if !defined(JSON_NOEXCEPTION)
T dom_value {};
T view_value{};
const std::string dom_error = json_view_test::exception_of_call([&] { dom_value = j.get<T>(); });
const std::string view_error = json_view_test::exception_of_call([&] { view_value = d.root().template get<T>(); });
CHECK(dom_error == view_error);
if (dom_error.empty())
{
CHECK(dom_value == view_value);
CHECK(view_value == static_cast<T>(b));
}
#else
// without exceptions, the combinations that throw (number_integer_t, number_unsigned_t, number_float_t)
// cannot be run: JSON_THROW aborts
const bool throws = std::is_same<T, json::number_integer_t>::value
|| std::is_same<T, json::number_unsigned_t>::value
|| std::is_same<T, json::number_float_t>::value;
if (!throws)
{
CHECK(j.get<T>() == d.root().template get<T>());
}
#endif
}
}
} // namespace
TEST_CASE("json_view get<T>() of a boolean (#5988)")
{
check_boolean_get_parity<bool>();
check_boolean_get_parity<char>();
check_boolean_get_parity<signed char>();
check_boolean_get_parity<unsigned char>();
check_boolean_get_parity<short>(); // NOLINT(google-runtime-int)
check_boolean_get_parity<unsigned short>(); // NOLINT(google-runtime-int)
check_boolean_get_parity<int>();
check_boolean_get_parity<unsigned>();
check_boolean_get_parity<long>(); // NOLINT(google-runtime-int)
check_boolean_get_parity<unsigned long>(); // NOLINT(google-runtime-int)
check_boolean_get_parity<long long>(); // NOLINT(google-runtime-int)
check_boolean_get_parity<unsigned long long>(); // NOLINT(google-runtime-int)
check_boolean_get_parity<std::int64_t>();
check_boolean_get_parity<std::uint64_t>();
check_boolean_get_parity<float>();
check_boolean_get_parity<double>();
check_boolean_get_parity<long double>();
#if !defined(JSON_NOEXCEPTION)
SECTION("the exceptions of the exact number types")
{
const json_document d = json_document::parse("true");
CHECK_THROWS_WITH_AS(d.root().get<json::number_integer_t>(), json_view_test::message("type_error.302", "", 0, 4, "type must be number, but is boolean").c_str(), json::type_error&);
CHECK_THROWS_WITH_AS(d.root().get<json::number_unsigned_t>(), json_view_test::message("type_error.302", "", 0, 4, "type must be number, but is boolean").c_str(), json::type_error&);
CHECK_THROWS_WITH_AS(d.root().get<json::number_float_t>(), json_view_test::message("type_error.302", "", 0, 4, "type must be number, but is boolean").c_str(), json::type_error&);
CHECK(d.root().get<int>() == 1);
// get_to() and value() go through get<T>()
json::number_float_t x = 0;
CHECK_THROWS_AS(d.root().get_to(x), json::type_error&);
const json_document o = json_document::parse("{\"b\":true}");
CHECK_THROWS_AS(static_cast<void>(o.root().value("b", 0.0)), json::type_error&);
CHECK(o.root().value("b", 0) == 1);
}
#endif
}
TEST_CASE("json_view JSON pointers") TEST_CASE("json_view JSON pointers")
{ {
SECTION("every value of generated documents") SECTION("every value of generated documents")
+1 -1
View File
@@ -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 += ',';
+1 -2
View File
@@ -521,8 +521,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);