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
12 changed files with 196 additions and 313 deletions

No files matched your search

+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
+1 -11
View File
@@ -65,17 +65,7 @@ the same index, which is now a different value.
## Exception safety ## Exception safety
1. Strong guarantee: if an exception is thrown, there are no changes in the JSON value. Strong exception safety: if an exception occurs, the original value stays intact.
2. Basic guarantee: if an exception is thrown while the elements are copied (for example, `std::bad_alloc`), the JSON
value may be partially modified, but it stays a valid array.
3. Basic guarantee: if an exception is thrown while the elements are copied (for example, `std::bad_alloc`), the JSON
value may be partially modified, but it stays a valid array.
4. Basic guarantee: if an exception is thrown while the elements are copied (for example, `std::bad_alloc`), the JSON
value may be partially modified, but it stays a valid array.
5. Basic guarantee: if an exception is thrown while the members are copied (for example, `std::bad_alloc`), the JSON
value may be partially modified, but it stays a valid object.
The exceptions listed below are thrown before the JSON value is changed.
## Exceptions ## Exceptions
+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
+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:
+4 -36
View File
@@ -4914,30 +4914,6 @@ public:
return {it, res.second}; return {it, res.second};
} }
private:
/// Sets the parents of a value when it goes out of scope, also when an
/// exception is thrown: a container insertion that throws may already
/// have added elements, whose parents must be set for the value to stay
/// valid (basic exception guarantee). Does nothing without JSON_DIAGNOSTICS.
class set_parents_on_exit
{
public:
explicit set_parents_on_exit(basic_json* v) noexcept : m_value(v) {}
set_parents_on_exit(const set_parents_on_exit&) = delete;
set_parents_on_exit& operator=(const set_parents_on_exit&) = delete;
set_parents_on_exit(set_parents_on_exit&&) = delete;
set_parents_on_exit& operator=(set_parents_on_exit&&) = delete;
~set_parents_on_exit()
{
m_value->set_parents();
}
private:
basic_json* m_value;
};
public:
/// Helper for insertion of an iterator /// Helper for insertion of an iterator
/// @note: This uses std::distance to support GCC 4.8, /// @note: This uses std::distance to support GCC 4.8,
/// see https://github.com/nlohmann/json/pull/1257 /// see https://github.com/nlohmann/json/pull/1257
@@ -4947,7 +4923,6 @@ public:
iterator result(this); iterator result(this);
JSON_ASSERT(m_data.m_value.array != nullptr); JSON_ASSERT(m_data.m_value.array != nullptr);
const set_parents_on_exit parents(this);
auto insert_pos = std::distance(m_data.m_value.array->begin(), pos.m_it.array_iterator); auto insert_pos = std::distance(m_data.m_value.array->begin(), pos.m_it.array_iterator);
m_data.m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...); m_data.m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...);
result.m_it.array_iterator = m_data.m_value.array->begin() + insert_pos; result.m_it.array_iterator = m_data.m_value.array->begin() + insert_pos;
@@ -4956,6 +4931,7 @@ public:
// result.m_it.array_iterator = m_data.m_value.array->insert(pos.m_it.array_iterator, cnt, val); // result.m_it.array_iterator = m_data.m_value.array->insert(pos.m_it.array_iterator, cnt, val);
// but the return value of insert is missing in GCC 4.8, so it is written this way instead. // but the return value of insert is missing in GCC 4.8, so it is written this way instead.
set_parents();
return result; return result;
} }
@@ -4975,16 +4951,8 @@ public:
throw_iterator_from_other_value(this); throw_iterator_from_other_value(this);
} }
// vector::insert has no effects if copying a single element to the // insert to array and return iterator
// end throws; elsewhere, it may already have shifted the elements, so return insert_iterator(pos, val);
// copy first and insert by move, which cannot throw but by allocating
if (pos.m_it.array_iterator == m_data.m_value.array->end())
{
return insert_iterator(pos, val);
}
basic_json tmp(val);
return insert_iterator(pos, std::move(tmp));
} }
/// @brief inserts element into array /// @brief inserts element into array
@@ -5116,8 +5084,8 @@ public:
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this); detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this);
} }
const set_parents_on_exit parents(this);
m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);
set_parents();
} }
/// @brief updates a JSON object from another object, overwriting existing keys /// @brief updates a JSON object from another object, overwriting existing keys
+4 -36
View File
@@ -42706,30 +42706,6 @@ public:
return {it, res.second}; return {it, res.second};
} }
private:
/// Sets the parents of a value when it goes out of scope, also when an
/// exception is thrown: a container insertion that throws may already
/// have added elements, whose parents must be set for the value to stay
/// valid (basic exception guarantee). Does nothing without JSON_DIAGNOSTICS.
class set_parents_on_exit
{
public:
explicit set_parents_on_exit(basic_json* v) noexcept : m_value(v) {}
set_parents_on_exit(const set_parents_on_exit&) = delete;
set_parents_on_exit& operator=(const set_parents_on_exit&) = delete;
set_parents_on_exit(set_parents_on_exit&&) = delete;
set_parents_on_exit& operator=(set_parents_on_exit&&) = delete;
~set_parents_on_exit()
{
m_value->set_parents();
}
private:
basic_json* m_value;
};
public:
/// Helper for insertion of an iterator /// Helper for insertion of an iterator
/// @note: This uses std::distance to support GCC 4.8, /// @note: This uses std::distance to support GCC 4.8,
/// see https://github.com/nlohmann/json/pull/1257 /// see https://github.com/nlohmann/json/pull/1257
@@ -42739,7 +42715,6 @@ public:
iterator result(this); iterator result(this);
JSON_ASSERT(m_data.m_value.array != nullptr); JSON_ASSERT(m_data.m_value.array != nullptr);
const set_parents_on_exit parents(this);
auto insert_pos = std::distance(m_data.m_value.array->begin(), pos.m_it.array_iterator); auto insert_pos = std::distance(m_data.m_value.array->begin(), pos.m_it.array_iterator);
m_data.m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...); m_data.m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...);
result.m_it.array_iterator = m_data.m_value.array->begin() + insert_pos; result.m_it.array_iterator = m_data.m_value.array->begin() + insert_pos;
@@ -42748,6 +42723,7 @@ public:
// result.m_it.array_iterator = m_data.m_value.array->insert(pos.m_it.array_iterator, cnt, val); // result.m_it.array_iterator = m_data.m_value.array->insert(pos.m_it.array_iterator, cnt, val);
// but the return value of insert is missing in GCC 4.8, so it is written this way instead. // but the return value of insert is missing in GCC 4.8, so it is written this way instead.
set_parents();
return result; return result;
} }
@@ -42767,16 +42743,8 @@ public:
throw_iterator_from_other_value(this); throw_iterator_from_other_value(this);
} }
// vector::insert has no effects if copying a single element to the // insert to array and return iterator
// end throws; elsewhere, it may already have shifted the elements, so return insert_iterator(pos, val);
// copy first and insert by move, which cannot throw but by allocating
if (pos.m_it.array_iterator == m_data.m_value.array->end())
{
return insert_iterator(pos, val);
}
basic_json tmp(val);
return insert_iterator(pos, std::move(tmp));
} }
/// @brief inserts element into array /// @brief inserts element into array
@@ -42908,8 +42876,8 @@ public:
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this); detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this);
} }
const set_parents_on_exit parents(this);
m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);
set_parents();
} }
/// @brief updates a JSON object from another object, overwriting existing keys /// @brief updates a JSON object from another object, overwriting existing keys
+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:
-220
View File
@@ -12,7 +12,6 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using nlohmann::json; using nlohmann::json;
#include <functional>
#include <valarray> #include <valarray>
#if JSON_HAS_RANGES #if JSON_HAS_RANGES
#include <ranges> #include <ranges>
@@ -805,222 +804,3 @@ TEST_CASE("a failed allocation leaves the value unchanged")
#endif #endif
} }
#endif #endif
// the no-exceptions CI job has no std::bad_alloc to catch; with iterator
// debugging (MSVC STL debug builds) containers allocate inside noexcept
// functions, so failing an allocation there terminates the program
#if !defined(JSON_NOEXCEPTION) && !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
namespace
{
// whether every child of j names j as its parent (always true without
// JSON_DIAGNOSTICS, where no parents are stored)
template<class BasicJsonType>
bool children_name_their_parent(const BasicJsonType& j)
{
#if JSON_DIAGNOSTICS
if (j.is_array())
{
for (const auto& element : *j.m_data.m_value.array)
{
if (element.m_parent != &j)
{
return false;
}
}
}
else if (j.is_object())
{
for (const auto& element : *j.m_data.m_value.object)
{
if (element.second.m_parent != &j)
{
return false;
}
}
}
#else
static_cast<void>(j);
#endif
return true;
}
enum class guarantee
{
basic, // the value stays valid and keeps its type
strong // the value is unchanged
};
// Runs op on a fresh value make() with the 0th, 1st, 2nd, ... allocation made
// by the library failing in turn, until op completes. Every failed attempt
// must have thrown std::bad_alloc and left the value valid, with all parent
// pointers intact (copying a value asserts the invariant), and for the strong
// guarantee exactly as it was.
template<class BasicJsonType>
void check_exception_guarantee(guarantee g,
const std::function<BasicJsonType()>& make,
const std::function<void(BasicJsonType&)>& op)
{
fail_at_alloc_call = -1;
bool completed = false;
std::size_t failures = 0;
for (std::size_t n = 0; !completed; ++n)
{
CAPTURE(n)
REQUIRE(n < 1000);
BasicJsonType j = make();
const std::string before = j.dump();
const auto type_before = j.type();
alloc_call_count = 0;
fail_at_alloc_call = static_cast<long>(n);
try
{
op(j);
completed = true;
}
catch (const std::bad_alloc&)
{
++failures;
}
fail_at_alloc_call = -1;
if (!completed)
{
CHECK((j.type() == type_before));
CHECK(children_name_their_parent(j));
const BasicJsonType copy(j); // NOLINT(performance-unnecessary-copy-initialization): the copy is the point, it allocates
CHECK(copy.dump() == j.dump());
if (g == guarantee::strong)
{
CHECK(j.dump() == before);
}
}
}
// the operation allocates, so at least one attempt must have failed
CHECK(failures > 0);
}
template<class BasicJsonType>
void check_insert_exception_guarantees()
{
using json_t = BasicJsonType;
using make_t = std::function<json_t()>;
// strings of this length do not fit into the small string buffer, so
// copying them allocates
const json_t val = std::string(64, 'x');
const json_t source = json_t::array({std::string(64, 'p'), std::string(64, 'q'), std::string(64, 'r')});
for (const bool spare_capacity :
{
false, true
})
{
CAPTURE(spare_capacity)
// [ "aaa...", "bbb...", "ccc..." ], with a spare slot if requested
const make_t make = [spare_capacity]()
{
json_t j = json_t::array({std::string(64, 'a'), std::string(64, 'b'), std::string(64, 'c')});
if (spare_capacity)
{
j.push_back(std::string(64, 'd'));
j.erase(3);
}
return json_t(std::move(j));
};
for (const bool at_end :
{
false, true
})
{
CAPTURE(at_end)
SECTION("insert(pos, const basic_json&)")
{
check_exception_guarantee<json_t>(guarantee::strong, make, [&](json_t& j)
{
j.insert(at_end ? j.end() : j.begin() + 1, val);
});
}
SECTION("insert(pos, basic_json&&)")
{
check_exception_guarantee<json_t>(guarantee::strong, make, [&](json_t& j)
{
j.insert(at_end ? j.end() : j.begin() + 1, json_t(val));
});
}
SECTION("insert(pos, size_type, const basic_json&)")
{
check_exception_guarantee<json_t>(guarantee::basic, make, [&](json_t& j)
{
j.insert(at_end ? j.end() : j.begin() + 1, 3, val);
});
}
SECTION("insert(pos, const_iterator, const_iterator)")
{
check_exception_guarantee<json_t>(guarantee::basic, make, [&](json_t& j)
{
j.insert(at_end ? j.end() : j.begin() + 1, source.begin(), source.end());
});
}
SECTION("insert(pos, initializer_list_t)")
{
check_exception_guarantee<json_t>(guarantee::basic, make, [&](json_t& j)
{
j.insert(at_end ? j.end() : j.begin() + 1, {val, val});
});
}
}
}
SECTION("insert(const_iterator, const_iterator) for objects")
{
const json_t members = {{"m", std::string(64, 'm')}, {"n", std::string(64, 'n')}, {"o", std::string(64, 'o')}};
SECTION("no member exists yet")
{
check_exception_guarantee<json_t>(guarantee::basic, []()
{
return json_t({{"a", 1}});
}, [&](json_t& j)
{
j.insert(members.begin(), members.end());
});
}
SECTION("some members exist already")
{
check_exception_guarantee<json_t>(guarantee::basic, []()
{
return json_t({{"a", 1}, {"n", std::string(64, 'x')}});
}, [&](json_t& j)
{
j.insert(members.begin(), members.end());
});
}
}
}
} // namespace
TEST_CASE("insert() provides its documented exception guarantees (#5986)")
{
SECTION("std::map-backed object_t")
{
check_insert_exception_guarantees<nlohmann::json::with_allocator_t<nth_alloc_fails_allocator>>();
}
SECTION("ordered_map-backed object_t")
{
check_insert_exception_guarantees<nlohmann::ordered_json::with_allocator_t<nth_alloc_fails_allocator>>();
}
}
#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
+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);