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 237 additions and 114 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
+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
@@ -35,9 +35,6 @@ Note that enabling this macro increases the size of every JSON value by two `std
overhead to parsing, copying JSON value objects, and the generation of error messages for exceptions. It also causes overhead to parsing, copying JSON value objects, and the generation of error messages for exceptions. It also causes
these values to be reported in those error messages. these values to be reported in those error messages.
If [`JSON_DIAGNOSTICS`](json_diagnostics.md) is also set, the message contains the path and the byte range, such as
`(/1) (bytes 4-8)`. An exception about the root value has no path and only the range, such as `(bytes 0-4)`.
## Default definition ## Default definition
The default value is `0` (position diagnostics are switched off). The default value is `0` (position diagnostics are switched off).
+10 -12
View File
@@ -198,19 +198,18 @@ class exception : public std::exception
// all other parent types (primitives) contribute no token // all other parent types (primitives) contribute no token
} }
// the root value has no path, but still has a byte range if (tokens.empty())
std::string prefix;
if (!tokens.empty())
{ {
const auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{}, return "";
[](const std::string & a, const std::string & b)
{
return concat(a, '/', detail::escape(b));
});
prefix = concat('(', str, ") ");
} }
return concat(prefix, get_byte_positions(leaf_element)); auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
[](const std::string & a, const std::string & b)
{
return concat(a, '/', detail::escape(b));
});
return concat('(', str, ") ", get_byte_positions(leaf_element));
#else #else
return get_byte_positions(leaf_element); return get_byte_positions(leaf_element);
#endif #endif
@@ -223,8 +222,7 @@ class exception : public std::exception
template<typename BasicJsonType> template<typename BasicJsonType>
static std::string get_byte_positions(const BasicJsonType* leaf_element) static std::string get_byte_positions(const BasicJsonType* leaf_element)
{ {
// a value-initialized iterator has no value (invalid_iterator.212) if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos))
if (leaf_element != nullptr && leaf_element->start_pos() != std::string::npos && leaf_element->end_pos() != std::string::npos)
{ {
return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") "); return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
} }
+13 -12
View File
@@ -234,7 +234,8 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
/// the context basic_json puts in front of the message of an exception about /// the context basic_json puts in front of the message of an exception about
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then /// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (also for the root) /// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (with JSON_DIAGNOSTICS, only
/// if there is a path)
/// ///
/// This and the throw helpers that take an error_site are templates, so that /// This and the throw helpers that take an error_site are templates, so that
/// the path and range code is instantiated only where an exception can be /// the path and range code is instantiated only where an exception can be
@@ -255,19 +256,19 @@ std::string diagnostics(const error_site site)
std::string context; std::string context;
#if JSON_DIAGNOSTICS #if JSON_DIAGNOSTICS
std::vector<std::string> tokens; std::vector<std::string> tokens;
// a value that is not found leaves no tokens, like the root const bool found = edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens);
static_cast<void>(edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens)); if (!found || tokens.empty())
if (!tokens.empty())
{ {
// the root value has no path, but still has a byte range // context is still empty; returning it keeps the copy elided (-Wnrvo)
std::string path; return context;
for (const std::string& token : tokens)
{
path.push_back('/');
path += detail::escape(token);
}
context = concat('(', path, ") ");
} }
std::string path;
for (const std::string& token : tokens)
{
path.push_back('/');
path += detail::escape(token);
}
context = concat('(', path, ") ");
#endif #endif
#if JSON_DIAGNOSTIC_POSITIONS #if JSON_DIAGNOSTIC_POSITIONS
std::size_t start = 0; std::size_t start = 0;
+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:
+10 -12
View File
@@ -5695,19 +5695,18 @@ class exception : public std::exception
// all other parent types (primitives) contribute no token // all other parent types (primitives) contribute no token
} }
// the root value has no path, but still has a byte range if (tokens.empty())
std::string prefix;
if (!tokens.empty())
{ {
const auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{}, return "";
[](const std::string & a, const std::string & b)
{
return concat(a, '/', detail::escape(b));
});
prefix = concat('(', str, ") ");
} }
return concat(prefix, get_byte_positions(leaf_element)); auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
[](const std::string & a, const std::string & b)
{
return concat(a, '/', detail::escape(b));
});
return concat('(', str, ") ", get_byte_positions(leaf_element));
#else #else
return get_byte_positions(leaf_element); return get_byte_positions(leaf_element);
#endif #endif
@@ -5720,8 +5719,7 @@ class exception : public std::exception
template<typename BasicJsonType> template<typename BasicJsonType>
static std::string get_byte_positions(const BasicJsonType* leaf_element) static std::string get_byte_positions(const BasicJsonType* leaf_element)
{ {
// a value-initialized iterator has no value (invalid_iterator.212) if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos))
if (leaf_element != nullptr && leaf_element->start_pos() != std::string::npos && leaf_element->end_pos() != std::string::npos)
{ {
return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") "); return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
} }
+41 -15
View File
@@ -5076,7 +5076,8 @@ void value_range(const document_data& d, const node* n, std::size_t& start, std:
/// the context basic_json puts in front of the message of an exception about /// the context basic_json puts in front of the message of an exception about
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then /// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (also for the root) /// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (with JSON_DIAGNOSTICS, only
/// if there is a path)
/// ///
/// This and the throw helpers that take an error_site are templates, so that /// This and the throw helpers that take an error_site are templates, so that
/// the path and range code is instantiated only where an exception can be /// the path and range code is instantiated only where an exception can be
@@ -5097,19 +5098,19 @@ std::string diagnostics(const error_site site)
std::string context; std::string context;
#if JSON_DIAGNOSTICS #if JSON_DIAGNOSTICS
std::vector<std::string> tokens; std::vector<std::string> tokens;
// a value that is not found leaves no tokens, like the root const bool found = edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens);
static_cast<void>(edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens)); if (!found || tokens.empty())
if (!tokens.empty())
{ {
// the root value has no path, but still has a byte range // context is still empty; returning it keeps the copy elided (-Wnrvo)
std::string path; return context;
for (const std::string& token : tokens)
{
path.push_back('/');
path += detail::escape(token);
}
context = concat('(', path, ") ");
} }
std::string path;
for (const std::string& token : tokens)
{
path.push_back('/');
path += detail::escape(token);
}
context = concat('(', path, ") ");
#endif #endif
#if JSON_DIAGNOSTIC_POSITIONS #if JSON_DIAGNOSTIC_POSITIONS
std::size_t start = 0; std::size_t start = 0;
@@ -10066,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)
@@ -10085,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
@@ -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
-33
View File
@@ -193,28 +193,6 @@ TEST_CASE("Better diagnostics with positions")
check_conversion(300); check_conversion(300);
} }
SECTION("exception about the root value (#5987)")
{
// the root has no path, but still has a byte range
const json root = json::parse("true");
const json nested = json::parse("[1, true]");
const json constructed = true;
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(root.get<std::string>(),
"[json.exception.type_error.302] (bytes 0-4) type must be string, but is boolean", json::type_error);
CHECK_THROWS_WITH_AS(nested[1].get<std::string>(),
"[json.exception.type_error.302] (/1) (bytes 4-8) type must be string, but is boolean", json::type_error);
#else
CHECK_THROWS_WITH_AS(root.get<std::string>(),
"[json.exception.type_error.302] (bytes 0-4) type must be string, but is boolean", json::type_error);
CHECK_THROWS_WITH_AS(nested[1].get<std::string>(),
"[json.exception.type_error.302] (bytes 4-8) type must be string, but is boolean", json::type_error);
#endif
// a value without positions gets no range and no stray whitespace
CHECK_THROWS_WITH_AS(constructed.get<std::string>(),
"[json.exception.type_error.302] type must be string, but is boolean", json::type_error);
}
SECTION("JSON patch add to primitive parent (#4292)") SECTION("JSON patch add to primitive parent (#4292)")
{ {
// the JSON Patch "add" target /foo/bar/baz has a string parent // the JSON Patch "add" target /foo/bar/baz has a string parent
@@ -265,14 +243,3 @@ TEST_CASE("values read from a binary format have no positions")
check_no_positions(j); check_no_positions(j);
} }
} }
TEST_CASE("comparing a value-initialized iterator throws invalid_iterator.212")
{
// such an iterator has no value to take the byte positions from; the
// comparison used to dereference the null pointer instead of throwing
json j = {1, 2};
json::iterator it{};
CHECK_THROWS_WITH_AS(it == j.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator);
CHECK_THROWS_WITH_AS(it < j.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator);
CHECK_THROWS_WITH_AS(j.begin() == it, "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", json::invalid_iterator);
}
+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")
+6 -23
View File
@@ -300,27 +300,6 @@ TEST_CASE("json_view materialize() source positions")
static_cast<void>(j.at("list").at(1).get<int>()); static_cast<void>(j.at("list").at(1).get<int>());
})); }));
} }
SECTION("exception about the root value (#5987)")
{
const json_document root = json_document::parse("true");
const json_document nested = json_document::parse("[1, true]");
CHECK(json_view_test::exception_of_call([&]
{
static_cast<void>(root.root().get<std::string>());
}) == "[json.exception.type_error.302] (bytes 0-4) type must be string, but is boolean");
#if JSON_DIAGNOSTICS
CHECK(json_view_test::exception_of_call([&]
{
static_cast<void>(nested.root()[1].get<std::string>());
}) == "[json.exception.type_error.302] (/1) (bytes 4-8) type must be string, but is boolean");
#else
CHECK(json_view_test::exception_of_call([&]
{
static_cast<void>(nested.root()[1].get<std::string>());
}) == "[json.exception.type_error.302] (bytes 4-8) type must be string, but is boolean");
#endif
}
#endif #endif
SECTION("edited documents") SECTION("edited documents")
@@ -571,8 +550,12 @@ TEST_CASE("json_view exceptions name the path and the bytes of the value")
expected_zz += "(bytes 12-35) key 'zz' not found"; expected_zz += "(bytes 12-35) key 'zz' not found";
CHECK(exception_of_call([&] { static_cast<void>(d.root().at(json::json_pointer("/people/0/zz"))); }) == expected_zz); CHECK(exception_of_call([&] { static_cast<void>(d.root().at(json::json_pointer("/people/0/zz"))); }) == expected_zz);
// the root has no path, but still its bytes (#5987) // the root has no path, and with JSON_DIAGNOSTICS then no bytes either (as basic_json)
const std::string expected_missing = "[json.exception.out_of_range.403] (bytes 0-79) key 'missing' not found"; std::string expected_missing = "[json.exception.out_of_range.403] ";
#if !JSON_DIAGNOSTICS
expected_missing += "(bytes 0-79) ";
#endif
expected_missing += "key 'missing' not found";
CHECK(exception_of_call([&] { static_cast<void>(d.root().at("missing")); }) == expected_missing); CHECK(exception_of_call([&] { static_cast<void>(d.root().at("missing")); }) == expected_missing);
} }