Compare commits

..
Author SHA1 Message Date
Niels Lohmann 0349745c4d Fix MSVC source-encoding portability in the wide-string position test
Use é escapes instead of a literal UTF-8-encoded 'é' inside the L""
literal, so the wide string's content does not depend on the compiler's
assumed source character set (MSVC without /utf-8 decodes raw non-ASCII
source bytes using the system code page rather than as UTF-8, which was
producing a wstring of unexpected length/content and failing the
ws.size()/end_pos() assertions on Windows CI).

Also reworded a comment that unintentionally embedded the literal
substring "TODO check", which clang-tidy's google-readability-todo check
flags regardless of quoting context.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-06 11:22:27 +02:00
Niels Lohmann 784c3ad13c Add missing diagnostic-positions test coverage (lifetime, input adapters, SAX)
Building on the merged unit-class_parser.cpp from #5417, add
characterization tests (regression protection for existing behavior, not a
behavior change) for JSON_DIAGNOSTIC_POSITIONS:

- value lifetime: copy ctor copies positions recursively, move ctor resets
  the moved-from value to npos, and mutating a parsed document (operator[],
  push_back, erase) leaves the parent's stale span and siblings' positions
  untouched while new values get npos.
- input adapters: wide-string input positions count transcoded UTF-8 bytes
  (not wide characters), BOM-prefixed input's start_pos() reflects the
  skipped 3-byte BOM, istringstream/ifstream/iterator-pair inputs report
  consistent (non-npos) positions, and binary formats (CBOR, MessagePack,
  UBJSON, BSON) always report npos.
- a user-constructed json_sax_dom_parser with no lexer (as used when driving
  json::sax_parse() directly) reports npos for every value, since it has no
  m_lexer_ref to source positions from.

While characterizing swap(), found that basic_json::swap() (and the friend
swap() that forwards to it) does not swap start_position/end_position,
unlike copy-assignment's operator=(basic_json), which does as part of its
copy-and-swap implementation. This looks like a real inconsistency/bug, but
per the scope of this test-only change it is only pinned (not fixed) here;
see the comment at the "swap() does NOT exchange positions" section.

Fixes #5420

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-05 20:57:37 +02:00
Niels Lohmann 6fb73ee33e De-duplicate the diagnostic-positions test files via define-based recompilation
tests/src/unit-class_parser_diagnostic_positions.cpp and
tests/src/unit-diagnostic-positions-only.cpp were maintained as near-copies
of unit-class_parser.cpp and unit-diagnostic-positions.cpp respectively, and
had drifted: trailing-comma handling, the #5342 filter-array/filter-value
sections, and the cross-input-adapter diagnostics test were never ported to
the positions-enabled copy.

Fold the position-specific assertions into the base files, guarded by
#if JSON_DIAGNOSTIC_POSITIONS / #if JSON_DIAGNOSTICS, and compile each base
file a second time with the relevant macro set via CMake COMPILE_DEFINITIONS
(mirroring the existing test-comparison_legacy pattern) instead of
maintaining a separate source file. This removes the duplication and, as a
side effect, closes the coverage gaps above since the full test file now
compiles under JSON_DIAGNOSTIC_POSITIONS=1 as well.

Fixes #5417

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-05 20:47:32 +02:00
16 changed files with 637 additions and 2118 deletions
-27
View File
@@ -1335,7 +1335,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief serialization
/// @sa https://json.nlohmann.me/api/basic_json/dump/
JSON_HEDLEY_WARN_UNUSED_RESULT
string_t dump(const int indent = -1,
const char indent_char = ' ',
const bool ensure_ascii = false,
@@ -1358,7 +1357,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return the type of the JSON value (explicit)
/// @sa https://json.nlohmann.me/api/basic_json/type/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr value_t type() const noexcept
{
return m_data.m_type;
@@ -1366,7 +1364,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether type is primitive
/// @sa https://json.nlohmann.me/api/basic_json/is_primitive/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_primitive() const noexcept
{
return is_null() || is_string() || is_boolean() || is_number() || is_binary();
@@ -1374,7 +1371,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether type is structured
/// @sa https://json.nlohmann.me/api/basic_json/is_structured/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_structured() const noexcept
{
return is_array() || is_object();
@@ -1382,7 +1378,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is null
/// @sa https://json.nlohmann.me/api/basic_json/is_null/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_null() const noexcept
{
return m_data.m_type == value_t::null;
@@ -1390,7 +1385,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a boolean
/// @sa https://json.nlohmann.me/api/basic_json/is_boolean/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_boolean() const noexcept
{
return m_data.m_type == value_t::boolean;
@@ -1398,7 +1392,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a number
/// @sa https://json.nlohmann.me/api/basic_json/is_number/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number() const noexcept
{
return is_number_integer() || is_number_float();
@@ -1406,7 +1399,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an integer number
/// @sa https://json.nlohmann.me/api/basic_json/is_number_integer/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number_integer() const noexcept
{
return m_data.m_type == value_t::number_integer || m_data.m_type == value_t::number_unsigned;
@@ -1414,7 +1406,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an unsigned integer number
/// @sa https://json.nlohmann.me/api/basic_json/is_number_unsigned/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number_unsigned() const noexcept
{
return m_data.m_type == value_t::number_unsigned;
@@ -1422,7 +1413,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a floating-point number
/// @sa https://json.nlohmann.me/api/basic_json/is_number_float/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number_float() const noexcept
{
return m_data.m_type == value_t::number_float;
@@ -1430,7 +1420,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an object
/// @sa https://json.nlohmann.me/api/basic_json/is_object/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_object() const noexcept
{
return m_data.m_type == value_t::object;
@@ -1438,7 +1427,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an array
/// @sa https://json.nlohmann.me/api/basic_json/is_array/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_array() const noexcept
{
return m_data.m_type == value_t::array;
@@ -1446,7 +1434,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a string
/// @sa https://json.nlohmann.me/api/basic_json/is_string/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_string() const noexcept
{
return m_data.m_type == value_t::string;
@@ -1454,7 +1441,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a binary array
/// @sa https://json.nlohmann.me/api/basic_json/is_binary/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_binary() const noexcept
{
return m_data.m_type == value_t::binary;
@@ -1462,7 +1448,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is discarded
/// @sa https://json.nlohmann.me/api/basic_json/is_discarded/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_discarded() const noexcept
{
return m_data.m_type == value_t::discarded;
@@ -2794,7 +2779,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief returns the number of occurrences of a key in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/count/
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type count(const typename object_t::key_type& key) const
{
// return 0 for all nonobject types
@@ -2805,7 +2789,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/count/
template<class KeyType, detail::enable_if_t<
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type count(KeyType && key) const
{
// return 0 for all nonobject types
@@ -2814,7 +2797,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief check the existence of an element in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/contains/
JSON_HEDLEY_WARN_UNUSED_RESULT
bool contains(const typename object_t::key_type& key) const
{
return is_object() && m_data.m_value.object->find(key) != m_data.m_value.object->end();
@@ -2824,7 +2806,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/contains/
template<class KeyType, detail::enable_if_t<
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
bool contains(KeyType && key) const
{
return is_object() && m_data.m_value.object->find(std::forward<KeyType>(key)) != m_data.m_value.object->end();
@@ -2832,14 +2813,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief check the existence of an element in a JSON object given a JSON pointer
/// @sa https://json.nlohmann.me/api/basic_json/contains/
JSON_HEDLEY_WARN_UNUSED_RESULT
bool contains(const json_pointer& ptr) const
{
return ptr.contains(this);
}
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
bool contains(const typename ::nlohmann::json_pointer<BasicJsonType>& ptr) const
{
@@ -2995,7 +2974,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief checks whether the container is empty.
/// @sa https://json.nlohmann.me/api/basic_json/empty/
JSON_HEDLEY_WARN_UNUSED_RESULT
bool empty() const noexcept
{
switch (m_data.m_type)
@@ -3035,7 +3013,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief returns the number of elements
/// @sa https://json.nlohmann.me/api/basic_json/size/
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type size() const noexcept
{
switch (m_data.m_type)
@@ -3075,7 +3052,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief returns the maximum possible number of elements
/// @sa https://json.nlohmann.me/api/basic_json/max_size/
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type max_size() const noexcept
{
switch (m_data.m_type)
@@ -4153,7 +4129,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief check if the input is valid JSON
/// @sa https://json.nlohmann.me/api/basic_json/accept/
template<typename InputType>
JSON_HEDLEY_WARN_UNUSED_RESULT
static bool accept(InputType&& i,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false)
@@ -4165,7 +4140,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/accept/
template<typename IteratorType, typename SentinelType = IteratorType,
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
static bool accept(IteratorType first, SentinelType last,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false)
@@ -4265,7 +4239,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return the type as string
/// @sa https://json.nlohmann.me/api/basic_json/type_name/
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_RETURNS_NON_NULL
const char* type_name() const noexcept
{
-27
View File
@@ -22763,7 +22763,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief serialization
/// @sa https://json.nlohmann.me/api/basic_json/dump/
JSON_HEDLEY_WARN_UNUSED_RESULT
string_t dump(const int indent = -1,
const char indent_char = ' ',
const bool ensure_ascii = false,
@@ -22786,7 +22785,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return the type of the JSON value (explicit)
/// @sa https://json.nlohmann.me/api/basic_json/type/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr value_t type() const noexcept
{
return m_data.m_type;
@@ -22794,7 +22792,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether type is primitive
/// @sa https://json.nlohmann.me/api/basic_json/is_primitive/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_primitive() const noexcept
{
return is_null() || is_string() || is_boolean() || is_number() || is_binary();
@@ -22802,7 +22799,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether type is structured
/// @sa https://json.nlohmann.me/api/basic_json/is_structured/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_structured() const noexcept
{
return is_array() || is_object();
@@ -22810,7 +22806,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is null
/// @sa https://json.nlohmann.me/api/basic_json/is_null/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_null() const noexcept
{
return m_data.m_type == value_t::null;
@@ -22818,7 +22813,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a boolean
/// @sa https://json.nlohmann.me/api/basic_json/is_boolean/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_boolean() const noexcept
{
return m_data.m_type == value_t::boolean;
@@ -22826,7 +22820,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a number
/// @sa https://json.nlohmann.me/api/basic_json/is_number/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number() const noexcept
{
return is_number_integer() || is_number_float();
@@ -22834,7 +22827,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an integer number
/// @sa https://json.nlohmann.me/api/basic_json/is_number_integer/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number_integer() const noexcept
{
return m_data.m_type == value_t::number_integer || m_data.m_type == value_t::number_unsigned;
@@ -22842,7 +22834,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an unsigned integer number
/// @sa https://json.nlohmann.me/api/basic_json/is_number_unsigned/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number_unsigned() const noexcept
{
return m_data.m_type == value_t::number_unsigned;
@@ -22850,7 +22841,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a floating-point number
/// @sa https://json.nlohmann.me/api/basic_json/is_number_float/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_number_float() const noexcept
{
return m_data.m_type == value_t::number_float;
@@ -22858,7 +22848,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an object
/// @sa https://json.nlohmann.me/api/basic_json/is_object/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_object() const noexcept
{
return m_data.m_type == value_t::object;
@@ -22866,7 +22855,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is an array
/// @sa https://json.nlohmann.me/api/basic_json/is_array/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_array() const noexcept
{
return m_data.m_type == value_t::array;
@@ -22874,7 +22862,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a string
/// @sa https://json.nlohmann.me/api/basic_json/is_string/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_string() const noexcept
{
return m_data.m_type == value_t::string;
@@ -22882,7 +22869,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is a binary array
/// @sa https://json.nlohmann.me/api/basic_json/is_binary/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_binary() const noexcept
{
return m_data.m_type == value_t::binary;
@@ -22890,7 +22876,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return whether value is discarded
/// @sa https://json.nlohmann.me/api/basic_json/is_discarded/
JSON_HEDLEY_WARN_UNUSED_RESULT
constexpr bool is_discarded() const noexcept
{
return m_data.m_type == value_t::discarded;
@@ -24222,7 +24207,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief returns the number of occurrences of a key in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/count/
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type count(const typename object_t::key_type& key) const
{
// return 0 for all nonobject types
@@ -24233,7 +24217,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/count/
template<class KeyType, detail::enable_if_t<
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type count(KeyType && key) const
{
// return 0 for all nonobject types
@@ -24242,7 +24225,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief check the existence of an element in a JSON object
/// @sa https://json.nlohmann.me/api/basic_json/contains/
JSON_HEDLEY_WARN_UNUSED_RESULT
bool contains(const typename object_t::key_type& key) const
{
return is_object() && m_data.m_value.object->find(key) != m_data.m_value.object->end();
@@ -24252,7 +24234,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/contains/
template<class KeyType, detail::enable_if_t<
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
bool contains(KeyType && key) const
{
return is_object() && m_data.m_value.object->find(std::forward<KeyType>(key)) != m_data.m_value.object->end();
@@ -24260,14 +24241,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief check the existence of an element in a JSON object given a JSON pointer
/// @sa https://json.nlohmann.me/api/basic_json/contains/
JSON_HEDLEY_WARN_UNUSED_RESULT
bool contains(const json_pointer& ptr) const
{
return ptr.contains(this);
}
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
bool contains(const typename ::nlohmann::json_pointer<BasicJsonType>& ptr) const
{
@@ -24423,7 +24402,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief checks whether the container is empty.
/// @sa https://json.nlohmann.me/api/basic_json/empty/
JSON_HEDLEY_WARN_UNUSED_RESULT
bool empty() const noexcept
{
switch (m_data.m_type)
@@ -24463,7 +24441,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief returns the number of elements
/// @sa https://json.nlohmann.me/api/basic_json/size/
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type size() const noexcept
{
switch (m_data.m_type)
@@ -24503,7 +24480,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief returns the maximum possible number of elements
/// @sa https://json.nlohmann.me/api/basic_json/max_size/
JSON_HEDLEY_WARN_UNUSED_RESULT
size_type max_size() const noexcept
{
switch (m_data.m_type)
@@ -25581,7 +25557,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief check if the input is valid JSON
/// @sa https://json.nlohmann.me/api/basic_json/accept/
template<typename InputType>
JSON_HEDLEY_WARN_UNUSED_RESULT
static bool accept(InputType&& i,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false)
@@ -25593,7 +25568,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @sa https://json.nlohmann.me/api/basic_json/accept/
template<typename IteratorType, typename SentinelType = IteratorType,
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
static bool accept(IteratorType first, SentinelType last,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false)
@@ -25693,7 +25667,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// @brief return the type as string
/// @sa https://json.nlohmann.me/api/basic_json/type_name/
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_RETURNS_NON_NULL
const char* type_name() const noexcept
{
+18
View File
@@ -177,6 +177,24 @@ json_test_add_test_for(src/unit-comparison.cpp
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
)
# test the parser again with JSON_DIAGNOSTIC_POSITIONS enabled
json_test_set_test_options(test-class_parser_diagnostic_positions
COMPILE_DEFINITIONS JSON_DIAGNOSTIC_POSITIONS=1
)
json_test_add_test_for(src/unit-class_parser.cpp
NAME test-class_parser_diagnostic_positions
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
)
# test diagnostic positions again without regular diagnostics (JSON pointer paths)
json_test_set_test_options(test-diagnostic-positions_only
COMPILE_DEFINITIONS JSON_DIAGNOSTICS=0
)
json_test_add_test_for(src/unit-diagnostic-positions.cpp
NAME test-diagnostic-positions_only
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
)
# *DO NOT* use json_test_set_test_options() below this line
#############################################################################
-9
View File
@@ -15,15 +15,6 @@
namespace utils
{
// Some tests intentionally discard the [[nodiscard]]/JSON_HEDLEY_WARN_UNUSED_RESULT
// return value of a call they only make to exercise its side effects (e.g. checking
// that it does not throw). A plain (void) cast on the call expression does not
// suppress GCC's warning for functions using the GNU __attribute__((warn_unused_result))
// form (as opposed to the C++17 [[nodiscard]] attribute) -- passing the value into an
// ordinary function call does.
template<typename T>
inline void ignore_return_value(T&& /*unused*/) noexcept {}
inline std::vector<std::uint8_t> read_binary_file(const std::string& filename)
{
std::ifstream file(filename, std::ios::binary);
+587 -4
View File
@@ -17,14 +17,14 @@ using nlohmann::json;
#include <valarray>
#include <algorithm>
#include <cstdio>
#include <fstream>
#include <list>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "test_utils.hpp"
namespace
{
class SaxEventLogger
@@ -346,6 +346,50 @@ void trailing_comma_helper(const std::string& s)
}
}
#if JSON_DIAGNOSTIC_POSITIONS
/**
* Validates that the generated JSON object is the same as expected
* Validates that the start position and end position match the start and end of the string
*
* This check assumes that there is no whitespace around the json object in the original string.
*/
void validate_generated_json_and_start_end_pos_helper(const std::string& original_string, const json& j, const json& check)
{
CHECK(j == check);
CHECK(j.start_pos() == 0);
CHECK(j.end_pos() == original_string.size());
}
/**
* Parses the root object from the given root string and validates that the start and end positions for the nested object are correct.
*
* This checks that whitespace around the nested object is included in the start and end positions of the root object.
*/
void validate_start_end_pos_for_nested_obj_helper(const std::string& nested_type_json_str, const std::string& root_type_json_str, const json& expected_json, const json::parser_callback_t& cb = nullptr)
{
json j;
// 1. If callback is provided, use callback version of parse()
if (cb)
{
j = json::parse(root_type_json_str, cb);
}
else
{
j = json::parse(root_type_json_str);
}
// 2. Check if the generated JSON is as expected
// Assumptions: The root_type_json_str does not have any whitespace around the json object
validate_generated_json_and_start_end_pos_helper(root_type_json_str, j, expected_json);
// 3. Get the nested object
const auto& nested = j["nested"];
// 4. Check if the start and end positions are generated correctly for nested objects and arrays
CHECK(nested_type_json_str == root_type_json_str.substr(nested.start_pos(), nested.end_pos() - nested.start_pos()));
}
#endif
} // namespace
TEST_CASE("parser class")
@@ -626,8 +670,7 @@ TEST_CASE("parser class")
SECTION("overflow")
{
// overflows during parsing yield an exception
// empty() is nodiscard; the exception is thrown by parser_helper() itself, before empty() would run
CHECK_THROWS_WITH_AS(utils::ignore_return_value(parser_helper("1.18973e+4932").empty()), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
CHECK_THROWS_WITH_AS(parser_helper("1.18973e+4932").empty(), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
}
SECTION("invalid numbers")
@@ -1782,6 +1825,228 @@ TEST_CASE("parser class")
CHECK_THROWS_WITH_AS(_ = json::parse("/a", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid comment; expecting '/' or '*' after '/'; last read: '/a'", json::parse_error);
CHECK_THROWS_WITH_AS(_ = json::parse("/*", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid comment; missing closing '*/'; last read: '/*<U+0000>'", json::parse_error);
}
#if JSON_DIAGNOSTIC_POSITIONS
// Macro for all test cases for start_pos and end_pos
#define SETUP_TESTCASES() \
SECTION("with callback") \
{ \
SECTION("filter nothing") \
{ \
json::parser_callback_t const cb = [](int /*unused*/, json::parse_event_t /*unused*/, json& /*unused*/) noexcept \
{ \
return true; \
}; \
validate_start_end_pos_for_nested_obj_helper(nested_type_json_str, root_type_json_str, expected, cb); \
} \
SECTION("filter element") \
{ \
json::parser_callback_t const cb = [](int /*unused*/, json::parse_event_t event, json& j) noexcept \
{ \
return (event != json::parse_event_t::key && event != json::parse_event_t::value) || j != json("a"); \
}; \
validate_start_end_pos_for_nested_obj_helper(nested_type_json_str, root_type_json_str, filteredExpected, cb); \
} \
} \
SECTION("without callback") \
{ \
validate_start_end_pos_for_nested_obj_helper(nested_type_json_str, root_type_json_str, expected); \
}
SECTION("retrieve start position and end position")
{
SECTION("for object")
{
// Create an object with spaces to test the start and end positions. Spaces will not be included in the
// JSON object, however, the start and end positions should include the spaces from the input JSON string.
const std::string nested_type_json_str = R"({ "a": 1,"b" : "test1"})";
const std::string root_type_json_str = R"({ "nested": )" + nested_type_json_str + R"(, "anotherValue": "test2"})";
auto expected = json({{"nested", {{"a", 1}, {"b", "test1"}}}, {"anotherValue", "test2"}});
auto filteredExpected = expected;
filteredExpected["nested"].erase("a");
SETUP_TESTCASES()
}
SECTION("for array")
{
const std::string nested_type_json_str = R"(["a", "test", 45])";
const std::string root_type_json_str = R"({ "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
auto expected = json({{"nested", {"a", "test", 45}}, {"anotherValue", "test"}});
auto filteredExpected = expected;
filteredExpected["nested"] = json({"test", 45});
SETUP_TESTCASES()
}
SECTION("for array with objects")
{
const std::string nested_type_json_str = R"([{"a": 1, "b": "test"}, {"c": 2, "d": "test2"}])";
const std::string root_type_json_str = R"({ "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
auto expected = json({{"nested", {{{"a", 1}, {"b", "test"}}, {{"c", 2}, {"d", "test2"}}}}, {"anotherValue", "test"}});
auto filteredExpected = expected;
filteredExpected["nested"][0].erase("a");
SETUP_TESTCASES()
auto j = json::parse(root_type_json_str);
auto nested_array = j["nested"];
const auto& nested_obj = nested_array[0];
CHECK(nested_type_json_str.substr(1, 21) == root_type_json_str.substr(nested_obj.start_pos(), nested_obj.end_pos() - nested_obj.start_pos()));
CHECK(nested_type_json_str.substr(24, 22) == root_type_json_str.substr(nested_array[1].start_pos(), nested_array[1].end_pos() - nested_array[1].start_pos()));
}
SECTION("for two levels of nesting objects")
{
const std::string nested_type_json_str = R"({"nested2": {"b": "test"}})";
const std::string root_type_json_str = R"({ "a": 2, "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
auto expected = json({{"a", 2}, {"nested", {{"nested2", {{"b", "test"}}}}}, {"anotherValue", "test"}});
auto filteredExpected = expected;
filteredExpected.erase("a");
SETUP_TESTCASES()
auto j = json::parse(root_type_json_str);
auto nested_obj = j["nested"]["nested2"];
CHECK(nested_type_json_str.substr(12, 13) == root_type_json_str.substr(nested_obj.start_pos(), nested_obj.end_pos() - nested_obj.start_pos()));
}
SECTION("for simple types")
{
SECTION("no nested")
{
SECTION("with callback")
{
json::parser_callback_t const cb = [](int /*unused*/, json::parse_event_t /*unused*/, json& /*unused*/) noexcept
{
return true;
};
// 1. string type
std::string json_str = R"("test")";
auto j = json::parse(json_str, cb);
validate_generated_json_and_start_end_pos_helper(json_str, j, "test");
// 2. number type
json_str = R"(1)";
j = json::parse(json_str, cb);
validate_generated_json_and_start_end_pos_helper(json_str, j, 1);
// 3. boolean type
json_str = R"(true)";
j = json::parse(json_str, cb);
validate_generated_json_and_start_end_pos_helper(json_str, j, true);
// 4. null type
json_str = R"(null)";
j = json::parse(json_str, cb);
validate_generated_json_and_start_end_pos_helper(json_str, j, nullptr);
}
SECTION("without callback")
{
// 1. string type
std::string json_str = R"("test")";
auto j = json::parse(json_str);
validate_generated_json_and_start_end_pos_helper(json_str, j, "test");
// 2. number type
json_str = R"(1)";
j = json::parse(json_str);
validate_generated_json_and_start_end_pos_helper(json_str, j, 1);
json_str = R"(1.001239923)";
j = json::parse(json_str);
validate_generated_json_and_start_end_pos_helper(json_str, j, 1.001239923);
json_str = R"(1.123812389000000)";
j = json::parse(json_str);
validate_generated_json_and_start_end_pos_helper(json_str, j, 1.123812389);
// 3. boolean type
json_str = R"(true)";
j = json::parse(json_str);
validate_generated_json_and_start_end_pos_helper(json_str, j, true);
json_str = R"(false)";
j = json::parse(json_str);
validate_generated_json_and_start_end_pos_helper(json_str, j, false);
// 4. null type
json_str = R"(null)";
j = json::parse(json_str);
validate_generated_json_and_start_end_pos_helper(json_str, j, nullptr);
}
}
SECTION("string type")
{
const std::string nested_type_json_str = R"("test")";
const std::string root_type_json_str = R"({ "a": 1, "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
auto expected = json({{"nested", "test"}, {"anotherValue", "test"}, {"a", 1}});
auto filteredExpected = expected;
filteredExpected.erase("a");
SETUP_TESTCASES()
}
SECTION("number type")
{
const std::string nested_type_json_str = R"(2)";
const std::string root_type_json_str = R"({ "a": 1, "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
auto expected = json({{"nested", 2}, {"anotherValue", "test"}, {"a", 1}});
auto filteredExpected = expected;
filteredExpected.erase("a");
SETUP_TESTCASES()
}
SECTION("boolean type")
{
const std::string nested_type_json_str = R"(true)";
const std::string root_type_json_str = R"({ "a": 1, "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
auto expected = json({{"nested", true}, {"anotherValue", "test"}, {"a", 1}});
auto filteredExpected = expected;
filteredExpected.erase("a");
SETUP_TESTCASES()
}
SECTION("null type")
{
const std::string nested_type_json_str = R"(null)";
const std::string root_type_json_str = R"({ "a": 1, "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
auto expected = json({{"nested", nullptr}, {"anotherValue", "test"}, {"a", 1}});
auto filteredExpected = expected;
filteredExpected.erase("a");
SETUP_TESTCASES()
}
}
SECTION("with leading whitespace and newlines around root JSON")
{
const std::string initial_whitespace = R"(
)";
const std::string nested_type_json_str = R"({
"a": 1,
"nested": {
"b": "test"
},
"anotherValue": "test"
})";
const std::string end_whitespace = R"(
)";
const std::string root_type_json_str = initial_whitespace + nested_type_json_str + end_whitespace;
auto expected = json({{"a", 1}, {"nested", {{"b", "test"}}}, {"anotherValue", "test"}});
auto j = json::parse(root_type_json_str);
// 2. Check if the generated JSON is as expected
CHECK(j == expected);
// 3. Check if the start and end positions do not include the surrounding whitespace
CHECK(j.start_pos() == initial_whitespace.size());
CHECK(j.end_pos() == root_type_json_str.size() - end_whitespace.size());
}
}
#undef SETUP_TESTCASES
#endif
}
// this test relies on parse errors being thrown, so it is skipped when
@@ -1890,3 +2155,321 @@ TEST_CASE("last-read diagnostics are identical across input adapters")
}
}
#endif // !defined(JSON_NOEXCEPTION)
// this test characterizes the current (documented-by-example, not otherwise
// specified) behavior of JSON_DIAGNOSTIC_POSITIONS positions with respect to
// value lifetime (copy/move/swap/mutation), the various input adapters, and
// user-driven SAX usage. It is regression protection, not a behavior
// specification: if any of these checks fail after a change to json.hpp,
// that change deliberately altered observable behavior and the test (and
// this comment) should be updated accordingly, rather than "fixed" blindly.
#if JSON_DIAGNOSTIC_POSITIONS
TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
{
SECTION("value lifetime")
{
SECTION("copy constructor copies positions, recursively")
{
// basic_json(const basic_json&) (json.hpp, around line 1192) copies
// start_position/end_position for the value itself; nested values
// are copied via their own copy constructor (through the copied
// object/array container), so positions are preserved throughout
// the whole tree.
const std::string s = R"({"a":1,"b":[1,2,3]})";
const json a = json::parse(s);
const json b = a; // NOLINT(performance-unnecessary-copy-initialization)
CHECK(b.start_pos() == a.start_pos());
CHECK(b.end_pos() == a.end_pos());
CHECK(b["b"].start_pos() == a["b"].start_pos());
CHECK(b["b"].end_pos() == a["b"].end_pos());
CHECK(b["b"][0].start_pos() == a["b"][0].start_pos());
CHECK(b["b"][0].end_pos() == a["b"][0].end_pos());
// sanity: the positions are meaningful (not all npos)
CHECK(b.start_pos() == 0);
CHECK(b.end_pos() == s.size());
}
SECTION("move constructor resets the moved-from value to npos")
{
// basic_json(basic_json&&) (json.hpp, around line 1265) copies
// other's start_position/end_position into *this and then resets
// other's to npos (see the cppcheck-suppress[accessForwarded]
// annotation there, which flags this reset as worth a second
// look). Only the top-level moved-from value is affected; its
// (moved-away) children are gone along with it.
const std::string s = R"({"a":1,"b":[1,2,3]})";
json a = json::parse(s);
const auto a_start = a.start_pos();
const auto a_end = a.end_pos();
const auto nested_start = a["b"].start_pos();
const auto nested_end = a["b"].end_pos();
const json b(std::move(a));
// the destination retains the original positions, recursively
CHECK(b.start_pos() == a_start);
CHECK(b.end_pos() == a_end);
CHECK(b["b"].start_pos() == nested_start);
CHECK(b["b"].end_pos() == nested_end);
// the moved-from value is reset to a null and reports npos
CHECK(a.is_null()); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
CHECK(a.start_pos() == std::string::npos); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
CHECK(a.end_pos() == std::string::npos); // NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move)
}
SECTION("swap() does NOT exchange positions (likely a real bug, see below)")
{
// NOTE (characterizing, not fixing, for #5420): basic_json::swap()
// (json.hpp, around line 3540, and the friend swap() that forwards
// to it) swaps m_data.m_type and m_data.m_value but -- unlike
// copy-assignment's operator=(basic_json) (json.hpp, around line
// 1291), which swaps start_position/end_position as part of its
// copy-and-swap implementation -- it never touches
// start_position/end_position. So after swap(a, b), the *values*
// of a and b are exchanged, but their *positions* are not: each
// ends up with its own original position describing the other's
// new content. This looks like an oversight/inconsistency rather
// than intended behavior, and is flagged to the maintainer; this
// test only pins the current (surprising) behavior so a fix (or a
// deliberate decision to keep it) shows up here as an intentional
// change rather than a silent regression.
json a = json::parse(R"({"a":1})");
json b = json::parse(R"([1,2,3,4,5])");
const auto a_start = a.start_pos();
const auto a_end = a.end_pos();
const auto b_start = b.start_pos();
const auto b_end = b.end_pos();
// both start at 0 (root values start right away), but their
// lengths (and thus end positions) differ, which is enough to
// tell after the swap whether positions actually moved with
// the values
CHECK(a_end != b_end);
using std::swap;
swap(a, b);
// values were exchanged as expected ...
CHECK(a == json::parse(R"([1,2,3,4,5])"));
CHECK(b == json::parse(R"({"a":1})"));
// ... but positions were NOT: each variable kept its own
// original position, now describing the other's content
CHECK(a.start_pos() == a_start);
CHECK(a.end_pos() == a_end);
CHECK(b.start_pos() == b_start);
CHECK(b.end_pos() == b_end);
}
SECTION("mutating a parsed document leaves positions of unrelated values untouched")
{
// Positions are recorded once, during parsing, and are not
// recomputed on mutation. As a consequence, after a mutation the
// parent's own recorded span may no longer describe its current
// (serialized) content -- it still describes what was originally
// parsed. This is characterized here as current behavior, not
// asserted to be desirable or specified.
SECTION("operator[] adding a new object key")
{
const std::string s = R"({"a":1})";
json j = json::parse(s);
const auto root_start = j.start_pos();
const auto root_end = j.end_pos();
const auto a_start = j["a"].start_pos();
const auto a_end = j["a"].end_pos();
j["c"] = 42;
// the newly-added value was never parsed, so it has no position
CHECK(j["c"].start_pos() == std::string::npos);
CHECK(j["c"].end_pos() == std::string::npos);
// the existing sibling's position is unaffected
CHECK(j["a"].start_pos() == a_start);
CHECK(j["a"].end_pos() == a_end);
// the parent's own recorded span is left as-is (now stale:
// it still reflects the original, shorter `{"a":1}` string)
CHECK(j.start_pos() == root_start);
CHECK(j.end_pos() == root_end);
}
SECTION("push_back on a parsed array")
{
const std::string s = R"([1,2,3])";
json j = json::parse(s);
const auto root_start = j.start_pos();
const auto root_end = j.end_pos();
const auto first_start = j[0].start_pos();
j.push_back(4);
CHECK(j.back().start_pos() == std::string::npos);
CHECK(j.back().end_pos() == std::string::npos);
CHECK(j[0].start_pos() == first_start);
CHECK(j.start_pos() == root_start);
CHECK(j.end_pos() == root_end);
}
SECTION("erase on a parsed array shifts elements but keeps their own positions")
{
const std::string s = R"([1,2,3])";
json j = json::parse(s);
const auto second_start = j[1].start_pos();
const auto third_start = j[2].start_pos();
const auto root_start = j.start_pos();
const auto root_end = j.end_pos();
j.erase(0);
// remaining elements moved down an index, but each one still
// reports the position it had *before* the erase (i.e. its
// position in the original source string, not a
// recalculated one)
CHECK(j[0].start_pos() == second_start);
CHECK(j[1].start_pos() == third_start);
// the parent's own recorded span is again left as-is
CHECK(j.start_pos() == root_start);
CHECK(j.end_pos() == root_end);
}
}
}
SECTION("input adapters")
{
SECTION("wide string input: positions count transcoded UTF-8 bytes, not wide characters")
{
// 'é' (U+00E9) is a single code unit in a wchar_t/UTF-16 string, but
// transcodes to 2 bytes in UTF-8; the lexer only ever sees the
// transcoded UTF-8 byte stream, so reported positions are byte
// offsets into that UTF-8 stream, not indices into the original
// std::wstring.
// é (rather than a literal 'é' byte sequence in this source
// file) so the wide-string literal's meaning does not depend on
// the compiler's assumed source character set (MSVC, without
// /utf-8, would otherwise decode the raw UTF-8 bytes using the
// system code page instead of as UTF-8)
const std::wstring ws = L"{\"a\":\"\u00e9\u00e9\"}";
CHECK(ws.size() == 10); // 10 wide characters
const json j = json::parse(ws);
CHECK(j.start_pos() == 0);
// the transcoded UTF-8 form is 2 bytes longer than the wide string,
// because each of the two 'é' characters becomes 2 UTF-8 bytes
CHECK(j.end_pos() == 12);
CHECK(j.end_pos() != ws.size());
const json& a = j["a"];
CHECK(a.start_pos() == 5);
CHECK(a.end_pos() == 11);
}
SECTION("BOM-prefixed input: start_pos() reflects the skipped 3-byte BOM")
{
const std::string s = "\xEF\xBB\xBF{\"a\":1}";
const json j = json::parse(s);
// the lexer silently skips the BOM before parsing the value, so
// the root value's recorded span starts right after it
CHECK(j.start_pos() == 3);
CHECK(j.end_pos() == s.size());
}
SECTION("std::istringstream: positions are consistent, not npos")
{
const std::string s = R"({"a":1,"b":2})";
std::istringstream ss(s);
const json j = json::parse(ss);
CHECK(j.start_pos() == 0);
CHECK(j.end_pos() == s.size());
CHECK(j["a"].start_pos() == 5);
}
SECTION("std::ifstream: positions are consistent, not npos")
{
const std::string s = R"({"a":1,"b":2})";
{
std::ofstream file("unit-class_parser_diagnostic_positions.tmp");
file << s;
}
{
std::ifstream f("unit-class_parser_diagnostic_positions.tmp");
const json j = json::parse(f);
CHECK(j.start_pos() == 0);
CHECK(j.end_pos() == s.size());
CHECK(j["a"].start_pos() == 5);
}
static_cast<void>(std::remove("unit-class_parser_diagnostic_positions.tmp"));
}
SECTION("iterator-pair input: positions are consistent, not npos")
{
const std::string s = R"({"a":1,"b":2})";
const json j = json::parse(s.begin(), s.end());
CHECK(j.start_pos() == 0);
CHECK(j.end_pos() == s.size());
CHECK(j["a"].start_pos() == 5);
}
SECTION("binary formats have no text positions")
{
// binary formats (CBOR, MessagePack, UBJSON, BSON, BJData) are
// parsed via detail::binary_reader, which never sets
// start_position/end_position on the values it produces (they
// have no notion of a text offset), so every value's position
// stays at its default of npos.
const json src = json::parse(R"({"a":1,"b":[1,2]})");
const json from_cbor = json::from_cbor(json::to_cbor(src));
CHECK(from_cbor.start_pos() == std::string::npos);
CHECK(from_cbor.end_pos() == std::string::npos);
CHECK(from_cbor["a"].start_pos() == std::string::npos);
CHECK(from_cbor["b"][0].start_pos() == std::string::npos);
const json from_msgpack = json::from_msgpack(json::to_msgpack(src));
CHECK(from_msgpack.start_pos() == std::string::npos);
CHECK(from_msgpack.end_pos() == std::string::npos);
const json from_ubjson = json::from_ubjson(json::to_ubjson(src));
CHECK(from_ubjson.start_pos() == std::string::npos);
CHECK(from_ubjson.end_pos() == std::string::npos);
const json from_bson_val = json::from_bson(json::to_bson(src));
CHECK(from_bson_val.start_pos() == std::string::npos);
CHECK(from_bson_val.end_pos() == std::string::npos);
}
}
SECTION("user-driven SAX consumers with no lexer report npos")
{
// json::parse() internally wires up its json_sax_dom_parser with a
// pointer to its own lexer (see parser.hpp), which is how positions
// get set at all. A user who constructs a json_sax_dom_parser
// directly (e.g. to drive it via json::sax_parse()) and does not
// supply a lexer pointer gets a consumer with m_lexer_ref == nullptr;
// every "if (m_lexer_ref)" guard in json_sax.hpp is then skipped, so
// every value it produces keeps its default, unset position (npos).
// This was previously true but silently unasserted (operator==
// ignores positions), see #5420.
json result;
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> sdp(result);
const std::string s = R"({"a":1,"b":[1,2,3]})";
CHECK(json::sax_parse(s, &sdp));
CHECK(result.start_pos() == std::string::npos);
CHECK(result.end_pos() == std::string::npos);
CHECK(result["a"].start_pos() == std::string::npos);
CHECK(result["a"].end_pos() == std::string::npos);
CHECK(result["b"][0].start_pos() == std::string::npos);
CHECK(result["b"][0].end_pos() == std::string::npos);
}
}
#endif
File diff suppressed because it is too large Load Diff
@@ -1,44 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
#ifdef JSON_DIAGNOSTICS
#undef JSON_DIAGNOSTICS
#endif
#define JSON_DIAGNOSTICS 0
#define JSON_DIAGNOSTIC_POSITIONS 1
#include <nlohmann/json.hpp>
using json = nlohmann::json;
TEST_CASE("Better diagnostics with positions only")
{
SECTION("invalid type")
{
const std::string json_invalid_string = R"(
{
"address": {
"street": "Fake Street",
"housenumber": "1"
}
}
)";
json j = json::parse(json_invalid_string);
CHECK_THROWS_WITH_AS(j.at("address").at("housenumber").get<int>(),
"[json.exception.type_error.302] (bytes 108-111) type must be number, but is string", json::type_error);
}
SECTION("invalid type without positions")
{
const json j = "foo";
CHECK_THROWS_WITH_AS(j.get<int>(),
"[json.exception.type_error.302] type must be number, but is string", json::type_error);
}
}
+13 -1
View File
@@ -8,7 +8,9 @@
#include "doctest_compatibility.h"
#define JSON_DIAGNOSTICS 1
#ifndef JSON_DIAGNOSTICS
#define JSON_DIAGNOSTICS 1
#endif
#define JSON_DIAGNOSTIC_POSITIONS 1
#include <nlohmann/json.hpp>
@@ -27,8 +29,13 @@ TEST_CASE("Better diagnostics with positions")
}
)";
json j = json::parse(json_invalid_string);
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(j.at("address").at("housenumber").get<int>(),
"[json.exception.type_error.302] (/address/housenumber) (bytes 108-111) type must be number, but is string", json::type_error);
#else
CHECK_THROWS_WITH_AS(j.at("address").at("housenumber").get<int>(),
"[json.exception.type_error.302] (bytes 108-111) type must be number, but is string", json::type_error);
#endif
}
SECTION("invalid type without positions")
@@ -74,7 +81,12 @@ TEST_CASE("Better diagnostics with positions")
// (/foo/bar); the position of that parent is reported in the message
const json doc = json::parse(R"({"foo":{"bar":"a string"}})");
const json patch = json::parse(R"([{"op":"add","path":"/foo/bar/baz","value":1}])");
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(doc.patch(patch),
"[json.exception.out_of_range.411] (/foo/bar) (bytes 14-24) cannot add value: the JSON Patch 'add' target's parent is of type string, but must be an object or array", json::out_of_range);
#else
CHECK_THROWS_WITH_AS(doc.patch(patch),
"[json.exception.out_of_range.411] (bytes 14-24) cannot add value: the JSON Patch 'add' target's parent is of type string, but must be an object or array", json::out_of_range);
#endif
}
}
+1 -3
View File
@@ -29,7 +29,6 @@ using nlohmann::json;
#include <limits>
#include <cstdio>
#include "make_test_data_available.hpp"
#include "test_utils.hpp"
#ifdef JSON_HAS_CPP_17
#include <variant>
@@ -1374,8 +1373,7 @@ TEST_CASE("regression tests 1")
std::array<uint8_t, 28> key1 = {{ 103, 92, 117, 48, 48, 48, 55, 92, 114, 215, 126, 214, 95, 92, 34, 174, 40, 71, 38, 174, 40, 71, 38, 223, 134, 247, 127, 0 }};
std::string const key1_str(reinterpret_cast<char*>(key1.data()));
json const j = key1_str;
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 10: 0x7E", json::type_error&);
CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 10: 0x7E", json::type_error&);
}
#if JSON_USE_IMPLICIT_CONVERSIONS
+6 -15
View File
@@ -31,8 +31,6 @@ using ordered_json = nlohmann::ordered_json;
#include <type_traits>
#include <utility>
#include "test_utils.hpp"
#ifdef JSON_HAS_CPP_17
#include <any>
#include <variant>
@@ -641,12 +639,7 @@ TEST_CASE("regression tests 2")
s += static_cast<char>(i);
}
dump_test["1"] = s;
// dump() is nodiscard; this only checks that dumping does not throw/crash.
// A (void) cast on the call itself does not suppress GCC's warning for the
// GNU warn_unused_result attribute (unlike a real C++17 [[nodiscard]]), so
// capture the result in a variable and discard that instead.
auto dump_result = dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace);
(void)dump_result;
dump_test.dump(-1, ' ', true, nlohmann::json::error_handler_t::replace);
}
}
@@ -738,14 +731,12 @@ TEST_CASE("regression tests 2")
{
const std::array<unsigned char, 23> data = {{0x81, 0xA4, 0x64, 0x61, 0x74, 0x61, 0xC4, 0x0F, 0x33, 0x30, 0x30, 0x32, 0x33, 0x34, 0x30, 0x31, 0x30, 0x37, 0x30, 0x35, 0x30, 0x31, 0x30}};
const json j = json::from_msgpack(data.data(), data.size());
// dump() is nodiscard; this only checks that dumping does not throw
CHECK_NOTHROW(
utils::ignore_return_value(
j.dump(4, // Indent
' ', // Indent char
false, // Ensure ascii
json::error_handler_t::strict // Error
)));
j.dump(4, // Indent
' ', // Indent char
false, // Ensure ascii
json::error_handler_t::strict // Error
));
}
SECTION("PR #2181 - regression bug with lvalue")
+6 -11
View File
@@ -15,8 +15,6 @@ using nlohmann::json;
#include <sstream>
#include <iomanip>
#include "test_utils.hpp"
TEST_CASE("serialization")
{
SECTION("operator<<")
@@ -86,9 +84,8 @@ TEST_CASE("serialization")
{
const json j = "ä\xA9ü";
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&);
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump(1, ' ', false, json::error_handler_t::strict)), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&);
CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&);
CHECK_THROWS_WITH_AS(j.dump(1, ' ', false, json::error_handler_t::strict), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&);
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"äü\"");
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"ä\xEF\xBF\xBDü\"");
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"\\u00e4\\ufffd\\u00fc\"");
@@ -98,9 +95,8 @@ TEST_CASE("serialization")
{
const json j = "123\xC2";
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0xC2", json::type_error&);
CHECK_THROWS_AS(utils::ignore_return_value(j.dump(1, ' ', false, json::error_handler_t::strict)), json::type_error&);
CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0xC2", json::type_error&);
CHECK_THROWS_AS(j.dump(1, ' ', false, json::error_handler_t::strict), json::type_error&);
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123\"");
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\"");
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd\"");
@@ -110,9 +106,8 @@ TEST_CASE("serialization")
{
const json j = "123\xF1\xB0\x34\x35\x36";
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 5: 0x34", json::type_error&);
CHECK_THROWS_AS(utils::ignore_return_value(j.dump(1, ' ', false, json::error_handler_t::strict)), json::type_error&);
CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 5: 0x34", json::type_error&);
CHECK_THROWS_AS(j.dump(1, ' ', false, json::error_handler_t::strict), json::type_error&);
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"123456\"");
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"123\xEF\xBF\xBD\x34\x35\x36\"");
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"123\\ufffd456\"");
+2 -5
View File
@@ -17,7 +17,6 @@ using nlohmann::json;
#include <sstream>
#include <iomanip>
#include "make_test_data_available.hpp"
#include "test_utils.hpp"
TEST_CASE("Unicode (1/5)" * doctest::skip())
{
@@ -241,8 +240,7 @@ void roundtrip(bool success_expected, const std::string& s)
if (success_expected)
{
// serialization succeeds
// dump() is nodiscard; this only checks that dumping does not throw
CHECK_NOTHROW(utils::ignore_return_value(j.dump()));
CHECK_NOTHROW(j.dump());
// exclude parse test for U+0000
if (s[0] != '\0')
@@ -261,8 +259,7 @@ void roundtrip(bool success_expected, const std::string& s)
else
{
// serialization fails
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
CHECK_THROWS_AS(j.dump(), json::type_error&);
// parsing JSON text fails
CHECK_THROWS_AS(_ = json::parse(ps), json::parse_error&);
+1 -3
View File
@@ -19,7 +19,6 @@ using nlohmann::json;
#include <iostream>
#include <iomanip>
#include "make_test_data_available.hpp"
#include "test_utils.hpp"
// this test suite uses static variables with non-trivial destructors
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
@@ -98,8 +97,7 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
else
{
// strict mode must throw if success is not expected
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
CHECK_THROWS_AS(j.dump(), json::type_error&);
// ignore and replace must create different dumps
CHECK(s_ignored != s_replaced);
+1 -3
View File
@@ -19,7 +19,6 @@ using nlohmann::json;
#include <iostream>
#include <iomanip>
#include "make_test_data_available.hpp"
#include "test_utils.hpp"
// this test suite uses static variables with non-trivial destructors
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
@@ -98,8 +97,7 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
else
{
// strict mode must throw if success is not expected
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
CHECK_THROWS_AS(j.dump(), json::type_error&);
// ignore and replace must create different dumps
CHECK(s_ignored != s_replaced);
+1 -3
View File
@@ -19,7 +19,6 @@ using nlohmann::json;
#include <iostream>
#include <iomanip>
#include "make_test_data_available.hpp"
#include "test_utils.hpp"
// this test suite uses static variables with non-trivial destructors
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
@@ -98,8 +97,7 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
else
{
// strict mode must throw if success is not expected
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
CHECK_THROWS_AS(j.dump(), json::type_error&);
// ignore and replace must create different dumps
CHECK(s_ignored != s_replaced);
+1 -3
View File
@@ -19,7 +19,6 @@ using nlohmann::json;
#include <iostream>
#include <iomanip>
#include "make_test_data_available.hpp"
#include "test_utils.hpp"
// this test suite uses static variables with non-trivial destructors
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
@@ -98,8 +97,7 @@ void check_utf8dump(bool success_expected, int byte1, int byte2 = -1, int byte3
else
{
// strict mode must throw if success is not expected
// dump() is nodiscard; the exception is thrown by dump() itself before it would return
CHECK_THROWS_AS(utils::ignore_return_value(j.dump()), json::type_error&);
CHECK_THROWS_AS(j.dump(), json::type_error&);
// ignore and replace must create different dumps
CHECK(s_ignored != s_replaced);