mirror of
https://github.com/nlohmann/json.git
synced 2026-10-03 13:10:33 +00:00
Merge remote-tracking branch 'origin/develop' into claude/fix-issue-3989-db7e45
Signed-off-by: Niels Lohmann <mail@nlohmann.me> # Conflicts: # include/nlohmann/detail/input/lexer.hpp # include/nlohmann/detail/input/parser.hpp # single_include/nlohmann/json.hpp # tests/src/fuzzer-parse_json.cpp
This commit is contained in:
@@ -3742,8 +3742,8 @@ class binary_reader
|
||||
return enter_object(detail::unknown_size());
|
||||
}
|
||||
|
||||
// Note, no reader for UBJSON binary types is implemented because they do
|
||||
// not exist
|
||||
// Note, UBJSON has no binary type of its own; BJData, which shares this
|
||||
// reader, decodes optimized 'B' arrays as binary in get_ubjson_array().
|
||||
|
||||
bool get_ubjson_high_precision_number()
|
||||
{
|
||||
@@ -4649,7 +4649,7 @@ class binary_reader
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
/*!
|
||||
@brief read a number from the input
|
||||
|
||||
@tparam NumberType the type of the number
|
||||
@@ -4659,10 +4659,10 @@ class binary_reader
|
||||
@return whether conversion completed
|
||||
|
||||
@note This function needs to respect the system's endianness, because
|
||||
bytes in CBOR, MessagePack, and UBJSON are stored in network order
|
||||
(big endian) and therefore need reordering on little endian systems.
|
||||
On the other hand, BSON and BJData use little endian and should reorder
|
||||
on big endian systems.
|
||||
bytes in CBOR, MessagePack, UBJSON, and BON8 are stored in network
|
||||
order (big endian) and therefore need reordering on little endian
|
||||
systems. On the other hand, BSON and BJData use little endian and
|
||||
should reorder on big endian systems.
|
||||
*/
|
||||
template<typename NumberType, bool InputIsLittleEndian = false>
|
||||
bool get_number(const input_format_t format, NumberType& result)
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // min
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstring> // strlen
|
||||
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next
|
||||
#include <memory> // shared_ptr, make_shared, addressof
|
||||
#include <numeric> // accumulate
|
||||
#include <streambuf> // streambuf
|
||||
#include <string> // string, char_traits
|
||||
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <nlohmann/detail/iterators/iterator_traits.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -82,8 +83,9 @@ class file_input_adapter
|
||||
};
|
||||
|
||||
/*!
|
||||
Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at
|
||||
beginning of input. Does not support changing the underlying std::streambuf
|
||||
Input adapter for a (caching) istream. Does not skip a UTF Byte Order Mark
|
||||
itself; that is done by the lexer's skip_bom(). Does not support changing
|
||||
the underlying std::streambuf
|
||||
in mid-input. Maintains underlying std::istream and std::streambuf to support
|
||||
subsequent use of standard std::istream operations to process any input
|
||||
characters following those used in parsing the JSON input. Clears the
|
||||
@@ -454,32 +456,14 @@ struct wide_string_input_helper<BaseInputAdapter, 4>
|
||||
// get the current character
|
||||
const auto wc = input.get_character();
|
||||
|
||||
// UTF-32 to UTF-8 encoding
|
||||
if (wc < 0x80)
|
||||
if (wc <= 0x10FFFF)
|
||||
{
|
||||
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);
|
||||
utf8_bytes_filled = 1;
|
||||
}
|
||||
else if (wc <= 0x7FF)
|
||||
{
|
||||
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xC0u | ((static_cast<unsigned int>(wc) >> 6u) & 0x1Fu));
|
||||
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));
|
||||
utf8_bytes_filled = 2;
|
||||
}
|
||||
else if (wc <= 0xFFFF)
|
||||
{
|
||||
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xE0u | ((static_cast<unsigned int>(wc) >> 12u) & 0x0Fu));
|
||||
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 6u) & 0x3Fu));
|
||||
utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));
|
||||
utf8_bytes_filled = 3;
|
||||
}
|
||||
else if (wc <= 0x10FFFF)
|
||||
{
|
||||
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | ((static_cast<unsigned int>(wc) >> 18u) & 0x07u));
|
||||
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 12u) & 0x3Fu));
|
||||
utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 6u) & 0x3Fu));
|
||||
utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));
|
||||
utf8_bytes_filled = 4;
|
||||
// UTF-32 to UTF-8 encoding
|
||||
utf8_bytes_filled = 0;
|
||||
encode_utf8(static_cast<std::uint32_t>(wc), [&utf8_bytes, &utf8_bytes_filled](std::uint32_t byte)
|
||||
{
|
||||
utf8_bytes[utf8_bytes_filled++] = static_cast<std::char_traits<char>::int_type>(byte);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -516,24 +500,15 @@ struct wide_string_input_helper<BaseInputAdapter, 2>
|
||||
// get the current character
|
||||
const auto wc = input.get_character();
|
||||
|
||||
// UTF-16 to UTF-8 encoding
|
||||
if (wc < 0x80)
|
||||
if (0xD800 > wc || wc >= 0xE000)
|
||||
{
|
||||
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);
|
||||
utf8_bytes_filled = 1;
|
||||
}
|
||||
else if (wc <= 0x7FF)
|
||||
{
|
||||
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xC0u | ((static_cast<unsigned int>(wc) >> 6u)));
|
||||
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));
|
||||
utf8_bytes_filled = 2;
|
||||
}
|
||||
else if (0xD800 > wc || wc >= 0xE000)
|
||||
{
|
||||
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xE0u | ((static_cast<unsigned int>(wc) >> 12u)));
|
||||
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 6u) & 0x3Fu));
|
||||
utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));
|
||||
utf8_bytes_filled = 3;
|
||||
// a UTF-16 code unit outside the surrogate range is a valid
|
||||
// code point (at most U+FFFF) on its own
|
||||
utf8_bytes_filled = 0;
|
||||
encode_utf8(static_cast<std::uint32_t>(wc), [&utf8_bytes, &utf8_bytes_filled](std::uint32_t byte)
|
||||
{
|
||||
utf8_bytes[utf8_bytes_filled++] = static_cast<std::char_traits<char>::int_type>(byte);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -551,11 +526,11 @@ struct wide_string_input_helper<BaseInputAdapter, 2>
|
||||
if (0xDC00 <= wc2 && wc2 <= 0xDFFF)
|
||||
{
|
||||
const auto charcode = 0x10000u + (((static_cast<unsigned int>(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu));
|
||||
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | (charcode >> 18u));
|
||||
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu));
|
||||
utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu));
|
||||
utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (charcode & 0x3Fu));
|
||||
utf8_bytes_filled = 4;
|
||||
utf8_bytes_filled = 0;
|
||||
encode_utf8(charcode, [&utf8_bytes, &utf8_bytes_filled](std::uint32_t byte)
|
||||
{
|
||||
utf8_bytes[utf8_bytes_filled++] = static_cast<std::char_traits<char>::int_type>(byte);
|
||||
});
|
||||
valid_pair = true;
|
||||
}
|
||||
}
|
||||
@@ -884,9 +859,9 @@ auto input_adapter(T (&array)[N]) -> decltype(input_adapter(array, array + N)) /
|
||||
return input_adapter(array, array + N);
|
||||
}
|
||||
|
||||
// This class only handles inputs of input_buffer_adapter type.
|
||||
// It's required so that expressions like {ptr, len} can be implicitly cast
|
||||
// to the correct adapter.
|
||||
// This class only handles inputs that construct a contiguous_bytes_input_adapter
|
||||
// (e.g. span_input_adapter). It's required so that expressions like {ptr, len}
|
||||
// can be implicitly cast to the correct adapter.
|
||||
class span_input_adapter
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <algorithm> // find_if, min
|
||||
#include <cstddef>
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <type_traits> // enable_if_t
|
||||
#include <utility> // move, pair
|
||||
@@ -177,6 +178,88 @@ template<typename ArrayType>
|
||||
inline void reserve_array(ArrayType& /*arr*/, std::size_t /*len*/, priority_tag<0> /*unused*/)
|
||||
{}
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
/*!
|
||||
@brief set the diagnostic positions of a value the DOM SAX parsers just stored
|
||||
|
||||
Shared by json_sax_dom_parser and json_sax_dom_callback_parser. basic_json
|
||||
befriends this struct, as the position members are private.
|
||||
*/
|
||||
struct diagnostic_positions
|
||||
{
|
||||
/*!
|
||||
@param[in,out] v the value that was just parsed
|
||||
@param[in] lexer the lexer that read it, or nullptr to leave @a v alone
|
||||
*/
|
||||
template<typename BasicJsonType, typename LexerType>
|
||||
static void set_from_lexer(BasicJsonType& v, LexerType* lexer)
|
||||
{
|
||||
if (lexer)
|
||||
{
|
||||
// Lexer has read past the current field value, so set the end position to the current position.
|
||||
// The start position will be set below based on the length of the string representation
|
||||
// of the value.
|
||||
v.end_position = lexer->get_position();
|
||||
|
||||
switch (v.type())
|
||||
{
|
||||
case value_t::boolean:
|
||||
{
|
||||
// 4 and 5 are the string length of "true" and "false"
|
||||
v.start_position = v.end_position - (v.m_data.m_value.boolean ? 4 : 5);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
{
|
||||
// 4 is the string length of "null"
|
||||
v.start_position = v.end_position - 4;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
// escape sequences make the token longer than the value it
|
||||
// parses to, so the start position cannot be derived from
|
||||
// the value; use the offset the lexer recorded instead
|
||||
v.start_position = lexer->get_token_start_position();
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::discarded:
|
||||
{
|
||||
// an object or array the callback of
|
||||
// json_sax_dom_callback_parser rejected has no position
|
||||
v.end_position = std::string::npos;
|
||||
v.start_position = v.end_position;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::binary:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
{
|
||||
v.start_position = v.end_position - lexer->get_string().size();
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
{
|
||||
// object and array are handled in start_object() and start_array() handlers
|
||||
// skip setting the values here.
|
||||
break;
|
||||
}
|
||||
default: // LCOV_EXCL_LINE
|
||||
// Handle all possible types discretely, default handler should never be reached.
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief SAX implementation to create a JSON value from SAX events
|
||||
|
||||
@@ -381,76 +464,6 @@ class json_sax_dom_parser
|
||||
|
||||
private:
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
void handle_diagnostic_positions_for_json_value(BasicJsonType& v)
|
||||
{
|
||||
if (m_lexer_ref)
|
||||
{
|
||||
// Lexer has read past the current field value, so set the end position to the current position.
|
||||
// The start position will be set below based on the length of the string representation
|
||||
// of the value.
|
||||
v.end_position = m_lexer_ref->get_position();
|
||||
|
||||
switch (v.type())
|
||||
{
|
||||
case value_t::boolean:
|
||||
{
|
||||
// 4 and 5 are the string length of "true" and "false"
|
||||
v.start_position = v.end_position - (v.m_data.m_value.boolean ? 4 : 5);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
{
|
||||
// 4 is the string length of "null"
|
||||
v.start_position = v.end_position - 4;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
// escape sequences make the token longer than the value it
|
||||
// parses to, so the start position cannot be derived from
|
||||
// the value; use the offset the lexer recorded instead
|
||||
v.start_position = m_lexer_ref->get_token_start_position();
|
||||
break;
|
||||
}
|
||||
|
||||
// As we handle the start and end positions for values created during parsing,
|
||||
// we do not expect the following value type to be called. Regardless, set the positions
|
||||
// in case this is created manually or through a different constructor. Exclude from lcov
|
||||
// since the exact condition of this switch is esoteric.
|
||||
// LCOV_EXCL_START
|
||||
case value_t::discarded:
|
||||
{
|
||||
v.end_position = std::string::npos;
|
||||
v.start_position = v.end_position;
|
||||
break;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
case value_t::binary:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
{
|
||||
v.start_position = v.end_position - m_lexer_ref->get_string().size();
|
||||
break;
|
||||
}
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
{
|
||||
// object and array are handled in start_object() and start_array() handlers
|
||||
// skip setting the values here.
|
||||
break;
|
||||
}
|
||||
default: // LCOV_EXCL_LINE
|
||||
// Handle all possible types discretely, default handler should never be reached.
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@invariant If the ref stack is empty, then the passed value will be the new
|
||||
root.
|
||||
@@ -466,7 +479,7 @@ class json_sax_dom_parser
|
||||
root = BasicJsonType(std::forward<Value>(v));
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
handle_diagnostic_positions_for_json_value(root);
|
||||
diagnostic_positions::set_from_lexer(root, m_lexer_ref);
|
||||
#endif
|
||||
|
||||
return &root;
|
||||
@@ -479,7 +492,7 @@ class json_sax_dom_parser
|
||||
ref_stack.back()->m_data.m_value.array->emplace_back(std::forward<Value>(v));
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
handle_diagnostic_positions_for_json_value(ref_stack.back()->m_data.m_value.array->back());
|
||||
diagnostic_positions::set_from_lexer(ref_stack.back()->m_data.m_value.array->back(), m_lexer_ref);
|
||||
#endif
|
||||
|
||||
return &(ref_stack.back()->m_data.m_value.array->back());
|
||||
@@ -490,7 +503,7 @@ class json_sax_dom_parser
|
||||
*object_element = BasicJsonType(std::forward<Value>(v));
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
handle_diagnostic_positions_for_json_value(*object_element);
|
||||
diagnostic_positions::set_from_lexer(*object_element, m_lexer_ref);
|
||||
#endif
|
||||
|
||||
return object_element;
|
||||
@@ -679,7 +692,7 @@ class json_sax_dom_callback_parser
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// Set start/end positions for discarded object.
|
||||
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
||||
diagnostic_positions::set_from_lexer(*ref_stack.back(), m_lexer_ref);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -795,7 +808,7 @@ class json_sax_dom_callback_parser
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// Set start/end positions for discarded array.
|
||||
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
||||
diagnostic_positions::set_from_lexer(*ref_stack.back(), m_lexer_ref);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -848,72 +861,6 @@ class json_sax_dom_callback_parser
|
||||
|
||||
private:
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
void handle_diagnostic_positions_for_json_value(BasicJsonType& v)
|
||||
{
|
||||
if (m_lexer_ref)
|
||||
{
|
||||
// Lexer has read past the current field value, so set the end position to the current position.
|
||||
// The start position will be set below based on the length of the string representation
|
||||
// of the value.
|
||||
v.end_position = m_lexer_ref->get_position();
|
||||
|
||||
switch (v.type())
|
||||
{
|
||||
case value_t::boolean:
|
||||
{
|
||||
// 4 and 5 are the string length of "true" and "false"
|
||||
v.start_position = v.end_position - (v.m_data.m_value.boolean ? 4 : 5);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
{
|
||||
// 4 is the string length of "null"
|
||||
v.start_position = v.end_position - 4;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
// escape sequences make the token longer than the value it
|
||||
// parses to, so the start position cannot be derived from
|
||||
// the value; use the offset the lexer recorded instead
|
||||
v.start_position = m_lexer_ref->get_token_start_position();
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::discarded:
|
||||
{
|
||||
v.end_position = std::string::npos;
|
||||
v.start_position = v.end_position;
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::binary:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
{
|
||||
v.start_position = v.end_position - m_lexer_ref->get_string().size();
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
{
|
||||
// object and array are handled in start_object() and start_array() handlers
|
||||
// skip setting the values here.
|
||||
break;
|
||||
}
|
||||
default: // LCOV_EXCL_LINE
|
||||
// Handle all possible types discretely, default handler should never be reached.
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// if there is a pending duplicate-key stash entry for this exact slot,
|
||||
/// remove it from the stash; if restore_value is true, the stashed
|
||||
/// previous value is moved back into the slot first (use this when the
|
||||
@@ -1035,7 +982,7 @@ class json_sax_dom_callback_parser
|
||||
auto value = BasicJsonType(std::forward<Value>(v));
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
handle_diagnostic_positions_for_json_value(value);
|
||||
diagnostic_positions::set_from_lexer(value, m_lexer_ref);
|
||||
#endif
|
||||
|
||||
// check callback
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <cstdio> // snprintf
|
||||
#include <initializer_list> // initializer_list
|
||||
#include <string> // char_traits, string
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <nlohmann/detail/input/string_scan.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -501,32 +502,10 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF);
|
||||
|
||||
// translate codepoint into bytes
|
||||
if (codepoint < 0x80)
|
||||
encode_utf8(static_cast<std::uint32_t>(codepoint), [this](std::uint32_t byte)
|
||||
{
|
||||
// 1-byte characters: 0xxxxxxx (ASCII)
|
||||
add(static_cast<char_int_type>(codepoint));
|
||||
}
|
||||
else if (codepoint <= 0x7FF)
|
||||
{
|
||||
// 2-byte characters: 110xxxxx 10xxxxxx
|
||||
add(static_cast<char_int_type>(0xC0u | (static_cast<unsigned int>(codepoint) >> 6u)));
|
||||
add(static_cast<char_int_type>(0x80u | (static_cast<unsigned int>(codepoint) & 0x3Fu)));
|
||||
}
|
||||
else if (codepoint <= 0xFFFF)
|
||||
{
|
||||
// 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx
|
||||
add(static_cast<char_int_type>(0xE0u | (static_cast<unsigned int>(codepoint) >> 12u)));
|
||||
add(static_cast<char_int_type>(0x80u | ((static_cast<unsigned int>(codepoint) >> 6u) & 0x3Fu)));
|
||||
add(static_cast<char_int_type>(0x80u | (static_cast<unsigned int>(codepoint) & 0x3Fu)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
add(static_cast<char_int_type>(0xF0u | (static_cast<unsigned int>(codepoint) >> 18u)));
|
||||
add(static_cast<char_int_type>(0x80u | ((static_cast<unsigned int>(codepoint) >> 12u) & 0x3Fu)));
|
||||
add(static_cast<char_int_type>(0x80u | ((static_cast<unsigned int>(codepoint) >> 6u) & 0x3Fu)));
|
||||
add(static_cast<char_int_type>(0x80u | (static_cast<unsigned int>(codepoint) & 0x3Fu)));
|
||||
}
|
||||
add(static_cast<char_int_type>(byte));
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -1424,45 +1403,30 @@ scan_number_done:
|
||||
*/
|
||||
token_type convert_number(token_type number_type, std::size_t mantissa_end)
|
||||
{
|
||||
// If the caller does not need the converted value (only whether the
|
||||
// input is syntactically valid; see json_sax_acceptor/accept()), an
|
||||
// unsigned/integer token can be reported without calling
|
||||
// strtoull()/strtoll() at all, *provided* we can already tell from
|
||||
// the digit count alone that the conversion cannot overflow 64 bits.
|
||||
// Such tokens are always finite and are accepted unconditionally by
|
||||
// the parser regardless of their actual value (parser::sax_parse_internal()
|
||||
// never checks finiteness for value_unsigned/value_integer), so the
|
||||
// classification below is all that is needed.
|
||||
// accept() only needs to know whether the input is valid, so it sets
|
||||
// discard_number_values (see json.hpp), and an integer token whose
|
||||
// digit count shows that it fits is reported without calling
|
||||
// convert_integer(). A number with up to 18 digits always fits into
|
||||
// both std::uint64_t and std::int64_t (18 nines is about 1e18, below
|
||||
// INT64_MAX, which is about 9.2e18). Longer tokens take the exact path
|
||||
// below, including the fallback to floating point when the value does
|
||||
// not fit.
|
||||
//
|
||||
// A decimal number with up to 18 digits is always representable in
|
||||
// both std::uint64_t and std::int64_t (18 nines is ~1e18, well below
|
||||
// both UINT64_MAX ~1.8e19 and INT64_MAX ~9.2e18), so strtoull()/strtoll()
|
||||
// could not have set errno to ERANGE for it. Numbers with more digits
|
||||
// (rare in practice) fall through to the exact code below, unchanged,
|
||||
// so their handling -- including reclassification to value_float when
|
||||
// the value overflows 64 bits, and rejection when it is not even
|
||||
// finite as a double -- is bit-for-bit identical to before this
|
||||
// optimization.
|
||||
// With a narrower number_unsigned_t/number_integer_t (e.g.
|
||||
// std::uint32_t), the exact path would reclassify some of these tokens
|
||||
// as (finite) floats, while this check reports integers. That does not
|
||||
// change the result of accept(): it always parses through
|
||||
// json_sax_acceptor, whose number callbacks discard their argument and
|
||||
// return true, and the parser rejects neither integers nor finite
|
||||
// floats. value_unsigned/value_integer are left unset here, so a caller
|
||||
// that reads the converted value must not set discard_number_values.
|
||||
//
|
||||
// Note this reasons about std::uint64_t/std::int64_t, not about
|
||||
// number_unsigned_t/number_integer_t (BasicJsonType's own, possibly
|
||||
// narrower, template parameters -- e.g. std::uint32_t). That is fine
|
||||
// *only* because discard_number_values is exclusively set by
|
||||
// accept() (see json.hpp), and accept() always parses through the
|
||||
// library's own json_sax_acceptor -- never a user-supplied SAX
|
||||
// consumer -- whose number_unsigned()/number_integer()/number_float()
|
||||
// callbacks unconditionally discard their argument and return true.
|
||||
// So for every caller that can reach this branch, neither the token
|
||||
// classification below nor the eventual (possibly narrowed, and on
|
||||
// this fast path left stale/unset) value_unsigned/value_integer is
|
||||
// ever consulted -- an unsigned/integer token is accepted outright,
|
||||
// and even a >18-digit token that this fast path deliberately falls
|
||||
// through for is, once reclassified to value_float, still finite
|
||||
// (and thus accepted) for any digit count that fits in number_unsigned_t
|
||||
// or number_integer_t regardless of that type's width. If this
|
||||
// function is ever taught to run with discard_number_values true for
|
||||
// a caller that *does* read the converted value, this reasoning (and
|
||||
// the fast path below) would need to be revisited.
|
||||
// On contiguous input, scan_number_bulk_contiguous() converts integer
|
||||
// tokens itself and does not pass them to this function, unless
|
||||
// JSON_DIAGNOSTIC_POSITIONS is enabled. This check is therefore only
|
||||
// reached for input without bulk access (e.g. streams), with
|
||||
// JSON_DIAGNOSTIC_POSITIONS, or when scan_number_bulk_contiguous()
|
||||
// falls back to scan_number().
|
||||
if (discard_number_values)
|
||||
{
|
||||
constexpr std::size_t safe_digit_count = 18;
|
||||
@@ -1891,7 +1855,7 @@ scan_number_done:
|
||||
return value_float;
|
||||
}
|
||||
|
||||
/// return current string value (implicitly resets the token; useful only once)
|
||||
/// return current string value
|
||||
string_t& get_string()
|
||||
{
|
||||
// a number token holds '.' regardless of the locale (#4084)
|
||||
@@ -2767,11 +2731,11 @@ scan_number_done:
|
||||
/// the position of the decimal point in token_buffer
|
||||
std::size_t decimal_point_position = std::string::npos;
|
||||
|
||||
/// whether the caller (e.g. accept()/json_sax_acceptor) only needs the
|
||||
/// token classification and never looks at the converted numeric value;
|
||||
/// when set, scan_number() may skip strtoull()/strtoll() for
|
||||
/// value_unsigned/value_integer tokens whose digit count guarantees they
|
||||
/// fit into 64 bits (see scan_number())
|
||||
/// whether the caller only needs the token types and never looks at the
|
||||
/// converted numeric values; set only by accept(), which parses through
|
||||
/// json_sax_acceptor. When set, convert_number() skips converting integer
|
||||
/// tokens whose digit count guarantees that they fit into 64 bits (see
|
||||
/// there)
|
||||
const bool discard_number_values = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -54,7 +54,8 @@ using parser_callback_t =
|
||||
/*!
|
||||
@brief syntax analysis
|
||||
|
||||
This class implements a recursive descent parser.
|
||||
This class implements an iterative parser that keeps the open containers on
|
||||
an explicit stack and reports what it reads as SAX events.
|
||||
*/
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
class parser
|
||||
@@ -98,28 +99,9 @@ class parser
|
||||
if (callback)
|
||||
{
|
||||
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
||||
sax_parse_internal<false>(&sdp);
|
||||
|
||||
if (strict)
|
||||
{
|
||||
// in strict mode, input must be completely read
|
||||
if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
sdp.parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(),
|
||||
exception_message(token_type::end_of_input, "value"), nullptr));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the caller keeps using the input: position it right after
|
||||
// the value by leaving the character that terminated it
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
|
||||
// in case of an error, return a discarded value
|
||||
if (sdp.is_errored())
|
||||
if (!parse_dom(sdp, strict))
|
||||
{
|
||||
result = value_t::discarded;
|
||||
return;
|
||||
@@ -135,26 +117,9 @@ class parser
|
||||
else
|
||||
{
|
||||
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
||||
sax_parse_internal<false>(&sdp);
|
||||
|
||||
if (strict)
|
||||
{
|
||||
// in strict mode, input must be completely read
|
||||
if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
sdp.parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// see above
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
|
||||
// in case of an error, return a discarded value
|
||||
if (sdp.is_errored())
|
||||
if (!parse_dom(sdp, strict))
|
||||
{
|
||||
result = value_t::discarded;
|
||||
return;
|
||||
@@ -239,6 +204,46 @@ class parser
|
||||
return result && !error_reported;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief run a DOM SAX parser to completion and position the lexer
|
||||
|
||||
Shared by both branches of @ref parse(): builds no SAX parser itself,
|
||||
but drives an already-constructed @a json_sax_dom_parser or
|
||||
@ref json_sax_dom_callback_parser through @ref sax_parse_internal(),
|
||||
then applies the strict-EOF check (reporting parse_error.101 through
|
||||
@a sdp on failure) or, in non-strict mode, releases the lookahead so
|
||||
the caller can keep reading the input right after the parsed value.
|
||||
|
||||
@param[in,out] sdp the DOM SAX parser to run
|
||||
@param[in] strict whether to expect the last token to be EOF
|
||||
@return whether @a sdp did not report an error
|
||||
*/
|
||||
template<typename DomSax>
|
||||
bool parse_dom(DomSax& sdp, const bool strict)
|
||||
{
|
||||
sax_parse_internal<false>(&sdp);
|
||||
|
||||
if (strict)
|
||||
{
|
||||
// in strict mode, input must be completely read
|
||||
if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
sdp.parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(),
|
||||
exception_message(token_type::end_of_input, "value"), nullptr));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the caller keeps using the input: position it right after
|
||||
// the value by leaving the character that terminated it
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
|
||||
return !sdp.is_errored();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief parse a JSON value and pass it to a SAX parser
|
||||
|
||||
@@ -554,8 +559,9 @@ class parser
|
||||
|
||||
// We are done with this array. Before we can parse a
|
||||
// new value, we need to evaluate the new state first.
|
||||
// By setting skip_to_state_evaluation to false, we
|
||||
// are effectively jumping to the beginning of this if.
|
||||
// By setting skip_to_state_evaluation to true, the next
|
||||
// iteration skips parsing a value and evaluates the
|
||||
// enclosing state directly.
|
||||
JSON_ASSERT(!states.empty());
|
||||
states.pop_back();
|
||||
skip_to_state_evaluation = true;
|
||||
@@ -639,8 +645,9 @@ class parser
|
||||
|
||||
// We are done with this object. Before we can parse a
|
||||
// new value, we need to evaluate the new state first.
|
||||
// By setting skip_to_state_evaluation to false, we
|
||||
// are effectively jumping to the beginning of this if.
|
||||
// By setting skip_to_state_evaluation to true, the next
|
||||
// iteration skips parsing a value and evaluates the
|
||||
// enclosing state directly.
|
||||
JSON_ASSERT(!states.empty());
|
||||
states.pop_back();
|
||||
skip_to_state_evaluation = true;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/output/output_adapters.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -2128,20 +2129,10 @@ class binary_writer
|
||||
const std::size_t valid = valid_utf8_prefix(data, s.size());
|
||||
if (JSON_HEDLEY_UNLIKELY(valid != s.size()))
|
||||
{
|
||||
JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(valid), ": 0x", hex_byte(data[valid])), &context));
|
||||
JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(valid), ": 0x", detail::hex_byte(data[valid])), &context));
|
||||
}
|
||||
}
|
||||
|
||||
/// @return a byte as two uppercase hexadecimal digits
|
||||
static std::string hex_byte(const std::uint8_t byte)
|
||||
{
|
||||
std::string result = "00";
|
||||
constexpr const char* nibble_to_hex = "0123456789ABCDEF";
|
||||
result[0] = nibble_to_hex[byte / 16];
|
||||
result[1] = nibble_to_hex[byte % 16];
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write an integer in the shortest encoding
|
||||
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // copy
|
||||
#include <cstddef> // size_t
|
||||
#include <iterator> // back_inserter
|
||||
#include <memory> // shared_ptr, make_shared
|
||||
#include <string> // basic_string
|
||||
#include <utility> // move
|
||||
@@ -31,6 +29,10 @@ namespace detail
|
||||
template<typename CharType> struct output_adapter_protocol
|
||||
{
|
||||
virtual void write_character(CharType c) = 0;
|
||||
/// @param[in] s pointer to the characters to write; binary_writer legitimately
|
||||
/// passes a null pointer together with length 0 for an empty
|
||||
/// string or binary value, so implementations must tolerate that
|
||||
/// @param[in] length number of characters at @a s
|
||||
virtual void write_characters(const CharType* s, std::size_t length) = 0;
|
||||
virtual ~output_adapter_protocol() = default;
|
||||
|
||||
@@ -97,7 +99,6 @@ class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
sink.write_character(c);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
void write_characters(const CharType* s, std::size_t length) override
|
||||
{
|
||||
sink.write_characters(s, length);
|
||||
@@ -122,7 +123,6 @@ class output_stream_adapter : public output_adapter_protocol<CharType>
|
||||
stream.put(c);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
void write_characters(const CharType* s, std::size_t length) override
|
||||
{
|
||||
stream.write(s, static_cast<std::streamsize>(length));
|
||||
@@ -147,7 +147,6 @@ class output_string_adapter : public output_adapter_protocol<CharType>
|
||||
str.push_back(c);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
void write_characters(const CharType* s, std::size_t length) override
|
||||
{
|
||||
str.append(s, length);
|
||||
|
||||
@@ -3,24 +3,23 @@
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2008, 2009 Björn Hoehrmann <bjoern@hoehrmann.de>
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // reverse, remove, fill, find, none_of, min
|
||||
#include <algorithm> // remove, fill, find, none_of, min
|
||||
#include <array> // array
|
||||
#include <clocale> // localeconv, lconv
|
||||
#include <cmath> // labs, isfinite, isnan, signbit
|
||||
#include <cmath> // isfinite
|
||||
#include <cstddef> // size_t, ptrdiff_t
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstdio> // snprintf
|
||||
#include <cstring> // memcpy, memset
|
||||
#include <iterator> // next
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string, char_traits
|
||||
#include <type_traits> // is_same
|
||||
#include <utility> // move
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/detail/conversions/to_chars.hpp>
|
||||
@@ -28,7 +27,6 @@
|
||||
#include <nlohmann/detail/input/string_scan.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||
#include <nlohmann/detail/output/binary_writer.hpp>
|
||||
#include <nlohmann/detail/output/output_adapters.hpp>
|
||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
@@ -83,7 +81,6 @@ class serializer
|
||||
const std::size_t indent_step_ = 0,
|
||||
error_handler_t error_handler_ = error_handler_t::strict)
|
||||
: o(&s)
|
||||
, locale(std::localeconv())
|
||||
, indent_char(ichar)
|
||||
, pretty_print(pretty_print_)
|
||||
, ensure_ascii(ensure_ascii_)
|
||||
@@ -106,9 +103,10 @@ class serializer
|
||||
additional parameter. Arrays and objects are serialized without recursion,
|
||||
however deeply they are nested.
|
||||
|
||||
- strings and object keys are escaped using `escape_string()`
|
||||
- integer numbers are converted implicitly via `operator<<`
|
||||
- floating-point numbers are converted to a string using `"%g"` format
|
||||
- strings and object keys are escaped using @ref dump_escaped
|
||||
- integer numbers are converted using a digit-pair lookup table (@ref dump_integer)
|
||||
- floating-point numbers are converted to a string using @ref dump_float, which
|
||||
uses `to_chars` for IEEE-754 types and `snprintf` otherwise
|
||||
- binary values are serialized as objects containing the subtype and the
|
||||
byte array
|
||||
|
||||
@@ -283,127 +281,16 @@ class serializer
|
||||
}
|
||||
|
||||
case value_t::string:
|
||||
{
|
||||
put_char('"');
|
||||
dump_escaped(*val.m_data.m_value.string);
|
||||
put_char('"');
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::binary:
|
||||
{
|
||||
if (pretty_print)
|
||||
{
|
||||
put_literal("{\n");
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = next_indent(current_indent, indent_step);
|
||||
|
||||
put_indent(new_indent);
|
||||
|
||||
put_literal("\"bytes\": [");
|
||||
|
||||
if (!val.m_data.m_value.binary->empty())
|
||||
{
|
||||
for (auto i = val.m_data.m_value.binary->cbegin();
|
||||
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
||||
{
|
||||
dump_byte(*i);
|
||||
put_literal(", ");
|
||||
}
|
||||
dump_byte(val.m_data.m_value.binary->back());
|
||||
}
|
||||
|
||||
put_literal("],\n");
|
||||
put_indent(new_indent);
|
||||
|
||||
put_literal("\"subtype\": ");
|
||||
if (val.m_data.m_value.binary->has_subtype())
|
||||
{
|
||||
dump_integer(val.m_data.m_value.binary->subtype());
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("null");
|
||||
}
|
||||
put_char('\n');
|
||||
put_indent(current_indent);
|
||||
put_char('}');
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("{\"bytes\":[");
|
||||
|
||||
if (!val.m_data.m_value.binary->empty())
|
||||
{
|
||||
for (auto i = val.m_data.m_value.binary->cbegin();
|
||||
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
||||
{
|
||||
dump_byte(*i);
|
||||
put_char(',');
|
||||
}
|
||||
dump_byte(val.m_data.m_value.binary->back());
|
||||
}
|
||||
|
||||
put_literal("],\"subtype\":");
|
||||
if (val.m_data.m_value.binary->has_subtype())
|
||||
{
|
||||
dump_integer(val.m_data.m_value.binary->subtype());
|
||||
put_char('}');
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("null}");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::boolean:
|
||||
{
|
||||
if (val.m_data.m_value.boolean)
|
||||
{
|
||||
put_literal("true");
|
||||
}
|
||||
else
|
||||
{
|
||||
put_literal("false");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
dump_integer(val.m_data.m_value.number_integer);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
dump_integer(val.m_data.m_value.number_unsigned);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::number_float:
|
||||
{
|
||||
dump_float(val.m_data.m_value.number_float);
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::discarded:
|
||||
{
|
||||
put_literal("<discarded>");
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
{
|
||||
put_literal("null");
|
||||
default:
|
||||
dump_scalar(val, current_indent);
|
||||
return;
|
||||
}
|
||||
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,9 +447,9 @@ class serializer
|
||||
@brief serialize the value @a val, but not the elements of a container
|
||||
|
||||
An object or array with elements is opened and pushed onto @a stack for
|
||||
@ref dump_internal to walk; everything else - including a binary value,
|
||||
@ref dump_iteratively to walk; everything else - including a binary value,
|
||||
which looks like an object but has no elements to descend into - is written
|
||||
out here in full.
|
||||
out in full by @ref dump_scalar.
|
||||
*/
|
||||
void dump_value(const BasicJsonType& val,
|
||||
const std::size_t current_indent,
|
||||
@@ -620,6 +507,35 @@ class serializer
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::string:
|
||||
case value_t::binary:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::discarded:
|
||||
case value_t::null:
|
||||
default:
|
||||
dump_scalar(val, current_indent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief serialize the value @a val, which is neither an object nor an array
|
||||
|
||||
Shared by @ref dump_internal and @ref dump_value, so that a value is written
|
||||
the same way however deeply it is nested. A binary value is written out here
|
||||
in full: it looks like an object, but has no elements to descend into.
|
||||
|
||||
@param[in] val value to serialize; not an object or array
|
||||
@param[in] current_indent the indentation of @a val, used for a
|
||||
pretty-printed binary value
|
||||
*/
|
||||
void dump_scalar(const BasicJsonType& val, const std::size_t current_indent)
|
||||
{
|
||||
switch (val.m_data.m_type)
|
||||
{
|
||||
case value_t::string:
|
||||
{
|
||||
put_char('"');
|
||||
@@ -740,6 +656,8 @@ class serializer
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::object: // LCOV_EXCL_LINE
|
||||
case value_t::array: // LCOV_EXCL_LINE
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
}
|
||||
@@ -768,7 +686,7 @@ class serializer
|
||||
Escape a string by replacing certain special characters by a sequence of an
|
||||
escape character (backslash) and another character and other control
|
||||
characters by a sequence of "\u" followed by a four-digit hex
|
||||
representation. The escaped string is written to output stream @a o.
|
||||
representation. The escaped string is appended to @ref write_buffer.
|
||||
|
||||
@param[in] s the string to escape
|
||||
|
||||
@@ -962,7 +880,7 @@ class serializer
|
||||
{
|
||||
case error_handler_t::strict:
|
||||
{
|
||||
JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_bytes(byte | 0)), nullptr));
|
||||
JSON_THROW(type_error::create(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", detail::hex_byte(byte)), nullptr));
|
||||
}
|
||||
|
||||
case error_handler_t::ignore:
|
||||
@@ -995,9 +913,9 @@ class serializer
|
||||
}
|
||||
else
|
||||
{
|
||||
string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xEF');
|
||||
string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xBF');
|
||||
string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xBD');
|
||||
string_buffer[bytes++] = '\xEF';
|
||||
string_buffer[bytes++] = '\xBF';
|
||||
string_buffer[bytes++] = '\xBD';
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
@@ -1054,7 +972,7 @@ class serializer
|
||||
{
|
||||
case error_handler_t::strict:
|
||||
{
|
||||
JSON_THROW(type_error::create(316, concat("incomplete UTF-8 string; last byte: 0x", hex_bytes(static_cast<std::uint8_t>(s[s.size() - 1] | 0))), nullptr));
|
||||
JSON_THROW(type_error::create(316, concat("incomplete UTF-8 string; last byte: 0x", detail::hex_byte(static_cast<std::uint8_t>(s[s.size() - 1]))), nullptr));
|
||||
}
|
||||
|
||||
case error_handler_t::ignore:
|
||||
@@ -1275,20 +1193,6 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief convert a byte to a uppercase hex representation
|
||||
* @param[in] byte byte to represent
|
||||
* @return representation ("00".."FF")
|
||||
*/
|
||||
static std::string hex_bytes(std::uint8_t byte)
|
||||
{
|
||||
std::string result = "FF";
|
||||
constexpr const char* nibble_to_hex = "0123456789ABCDEF";
|
||||
result[0] = nibble_to_hex[byte / 16];
|
||||
result[1] = nibble_to_hex[byte % 16];
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief write a lowercase "\uXXXX" escape sequence into @a string_buffer
|
||||
*
|
||||
@@ -1402,7 +1306,7 @@ class serializer
|
||||
/*!
|
||||
@brief dump an integer
|
||||
|
||||
Dump a given integer to output stream @a o. Works internally with
|
||||
Dump a given integer, appending it to @ref write_buffer. Works internally with
|
||||
@a number_buffer.
|
||||
|
||||
@param[in] x integer number (signed or unsigned) to dump
|
||||
@@ -1439,7 +1343,7 @@ class serializer
|
||||
}
|
||||
|
||||
// use a pointer to fill the buffer
|
||||
auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||
auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto)
|
||||
|
||||
number_unsigned_t abs_value;
|
||||
|
||||
@@ -1493,7 +1397,7 @@ class serializer
|
||||
/*!
|
||||
@brief dump a floating-point number
|
||||
|
||||
Dump a given floating-point number to output stream @a o. Works internally
|
||||
Dump a given floating-point number, appending it to @ref write_buffer. Works internally
|
||||
with @a number_buffer.
|
||||
|
||||
@param[in] x floating-point number to dump
|
||||
@@ -1554,21 +1458,28 @@ class serializer
|
||||
// check if the buffer was large enough
|
||||
JSON_ASSERT(static_cast<std::size_t>(len) < number_buffer.size());
|
||||
|
||||
// look up the locale's thousands separator and decimal point now,
|
||||
// matching what snprintf_float() just used (see lexer::get_decimal_point())
|
||||
const auto* loc = std::localeconv();
|
||||
JSON_ASSERT(loc != nullptr);
|
||||
const char thousands_sep = (loc->thousands_sep == nullptr) ? '\0' : *loc->thousands_sep;
|
||||
const char decimal_point = (loc->decimal_point == nullptr) ? '\0' : *loc->decimal_point;
|
||||
|
||||
// erase thousands separators
|
||||
if (locale.thousands_sep != '\0')
|
||||
if (thousands_sep != '\0')
|
||||
{
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, locale.thousands_sep);
|
||||
const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep);
|
||||
std::fill(end, number_buffer.end(), '\0');
|
||||
JSON_ASSERT((end - number_buffer.begin()) <= len);
|
||||
len = (end - number_buffer.begin());
|
||||
}
|
||||
|
||||
// convert decimal point to '.'
|
||||
if (locale.decimal_point != '\0' && locale.decimal_point != '.')
|
||||
if (decimal_point != '\0' && decimal_point != '.')
|
||||
{
|
||||
// NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081
|
||||
const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), locale.decimal_point);
|
||||
const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);
|
||||
if (dec_pos != number_buffer.end())
|
||||
{
|
||||
*dec_pos = '.';
|
||||
@@ -1613,34 +1524,17 @@ class serializer
|
||||
*/
|
||||
number_unsigned_t remove_sign(number_integer_t x) noexcept
|
||||
{
|
||||
JSON_ASSERT(x < 0 && x < (std::numeric_limits<number_integer_t>::max)()); // NOLINT(misc-redundant-expression)
|
||||
JSON_ASSERT(x < 0);
|
||||
return static_cast<number_unsigned_t>(-(x + 1)) + 1;
|
||||
}
|
||||
|
||||
private:
|
||||
/// the locale's thousand separator and decimal point characters
|
||||
struct locale_chars
|
||||
{
|
||||
explicit locale_chars(const std::lconv* loc) noexcept
|
||||
: thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep)))
|
||||
, decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point)))
|
||||
{}
|
||||
|
||||
const char thousands_sep;
|
||||
const char decimal_point;
|
||||
};
|
||||
|
||||
/// the output of the serializer (non-owning; the adapter lives at the call site)
|
||||
output_adapter_protocol<char>* o = nullptr;
|
||||
|
||||
/// a (hopefully) large enough character buffer
|
||||
std::array<char, 64> number_buffer{{}};
|
||||
|
||||
/// computed once from std::localeconv() at construction; @ref
|
||||
/// locale_chars keeps std::localeconv()'s pointer from having to be held
|
||||
/// past the constructor, while still letting these stay const
|
||||
const locale_chars locale;
|
||||
|
||||
/// string buffer
|
||||
std::array<char, 512> string_buffer{{}};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2008, 2009 Björn Hoehrmann <bjoern@hoehrmann.de>
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
@@ -37,6 +38,71 @@ StringType to_string(std::size_t value)
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @return a byte as two uppercase hexadecimal digits
|
||||
inline std::string hex_byte(const std::uint8_t byte)
|
||||
{
|
||||
std::string result = "00";
|
||||
constexpr const char* nibble_to_hex = "0123456789ABCDEF";
|
||||
result[0] = nibble_to_hex[byte / 16];
|
||||
result[1] = nibble_to_hex[byte % 16];
|
||||
return result;
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// UTF-8 encoding //
|
||||
///////////////////
|
||||
|
||||
/*!
|
||||
@brief encode a Unicode code point as UTF-8
|
||||
|
||||
Used to turn a decoded code point back into bytes: by the wide-string input
|
||||
adapters in input_adapters.hpp (one code point per UTF-32 unit, per UTF-16
|
||||
unit outside the surrogate range, and per valid UTF-16 surrogate pair), and
|
||||
by the lexer's `\uXXXX`/`\uXXXX\uYYYY` handling in lexer.hpp. Passing a
|
||||
code point above U+10FFFF, or one in the surrogate range U+D800..U+DFFF, is
|
||||
undefined behavior; callers are expected to have rejected those already
|
||||
(the wide-string adapters pass malformed units through unencoded instead of
|
||||
calling this function, and the lexer rejects unpaired surrogates before
|
||||
reaching it).
|
||||
|
||||
@tparam Out a callable invoked with one byte (as std::uint32_t, 0x00..0xFF)
|
||||
at a time, most significant byte first
|
||||
@param[in] cp the code point to encode (at most U+10FFFF)
|
||||
@param[in] out called once for each byte of the UTF-8 encoding of @a cp
|
||||
*/
|
||||
template<typename Out>
|
||||
void encode_utf8(std::uint32_t cp, Out&& out)
|
||||
{
|
||||
JSON_ASSERT(cp <= 0x10FFFF);
|
||||
|
||||
if (cp < 0x80)
|
||||
{
|
||||
// 1-byte characters: 0xxxxxxx (ASCII)
|
||||
out(cp);
|
||||
}
|
||||
else if (cp <= 0x7FF)
|
||||
{
|
||||
// 2-byte characters: 110xxxxx 10xxxxxx
|
||||
out(0xC0u | (cp >> 6u));
|
||||
out(0x80u | (cp & 0x3Fu));
|
||||
}
|
||||
else if (cp <= 0xFFFF)
|
||||
{
|
||||
// 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx
|
||||
out(0xE0u | (cp >> 12u));
|
||||
out(0x80u | ((cp >> 6u) & 0x3Fu));
|
||||
out(0x80u | (cp & 0x3Fu));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
out(0xF0u | (cp >> 18u));
|
||||
out(0x80u | ((cp >> 12u) & 0x3Fu));
|
||||
out(0x80u | ((cp >> 6u) & 0x3Fu));
|
||||
out(0x80u | (cp & 0x3Fu));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// UTF-8 decoding //
|
||||
///////////////////
|
||||
@@ -52,11 +118,23 @@ This is a single-byte step of a "shift-based" UTF-8 decoder originally
|
||||
written by Björn Hoehrmann. See
|
||||
http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
|
||||
|
||||
This decoder is the single source of truth for UTF-8 validation in this
|
||||
library: it is used both by the serializer (to escape and, in strict mode,
|
||||
reject ill-formed UTF-8 when dumping a string) and by the binary readers
|
||||
(to reject ill-formed UTF-8 in CBOR/MessagePack/BSON/UBJSON text strings at
|
||||
decode time; see @ref is_valid_utf8 below).
|
||||
The library checks UTF-8 well-formedness (RFC 3629, section 4) in four
|
||||
places, which differ in speed, diagnostics, and how they read the input:
|
||||
|
||||
- decode() and @ref is_valid_utf8 below: the serializer (to escape and, in
|
||||
strict mode, reject ill-formed UTF-8 when dumping a string) and the CBOR,
|
||||
MessagePack, BSON, UBJSON and BJData readers (to reject ill-formed UTF-8 in
|
||||
text strings at decode time).
|
||||
- the per-lead-byte switch in lexer::scan_string(): JSON text, with a
|
||||
diagnostic for each kind of error.
|
||||
- validate_one_utf8() and valid_utf8_prefix() in string_scan.hpp: the lexer's
|
||||
bulk string scan, the bulk path of the BON8 reader, and the BON8 writer.
|
||||
They must accept exactly what the lexer's switch accepts.
|
||||
- the byte path of binary_reader::get_bon8_string(): BON8 input without bulk
|
||||
access, and the bytes the bulk path leaves to it.
|
||||
|
||||
All four must accept the same set of sequences, so a change to one needs a
|
||||
matching change to the others.
|
||||
|
||||
@param[in,out] state the current decoder state
|
||||
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
|
||||
|
||||
+513
-220
@@ -29,7 +29,9 @@
|
||||
#endif
|
||||
|
||||
#include <algorithm> // all_of, find, for_each, none_of
|
||||
#include <cmath> // isnan
|
||||
#include <cstddef> // nullptr_t, ptrdiff_t, size_t
|
||||
#include <cstdint> // uint8_t
|
||||
#include <functional> // hash, less
|
||||
#include <initializer_list> // initializer_list
|
||||
#ifndef JSON_NO_IO
|
||||
@@ -38,19 +40,28 @@
|
||||
#include <iterator> // make_move_iterator, random_access_iterator_tag
|
||||
#include <limits> // numeric_limits
|
||||
#include <memory> // unique_ptr
|
||||
#include <set> // swap, operator!=
|
||||
#include <stdexcept> // length_error
|
||||
#include <string> // string, stoi, to_string
|
||||
#include <type_traits> // enable_if_t, is_same, is_scalar, ...
|
||||
#include <unordered_map> // swap (for the from_json(..., std::unordered_map&) overload)
|
||||
#include <utility> // declval, forward, move, pair, swap
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/adl_serializer.hpp>
|
||||
// keep: json.hpp's own basic_json<> default template arguments need the complete definition of
|
||||
// each of these, not only the forward declarations from json_fwd.hpp, so IWYU's suggestion to
|
||||
// drop them (nothing in this file otherwise names the type) would break every downstream
|
||||
// translation unit that relies on basic_json<>'s defaults actually being usable.
|
||||
#include <nlohmann/adl_serializer.hpp> // IWYU pragma: keep
|
||||
#include <nlohmann/byte_container_with_subtype.hpp>
|
||||
#include <nlohmann/detail/conversions/from_json.hpp>
|
||||
#include <nlohmann/detail/conversions/to_json.hpp>
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/conversions/from_json.hpp> // IWYU pragma: keep
|
||||
#include <nlohmann/detail/conversions/to_json.hpp> // IWYU pragma: keep
|
||||
#include <nlohmann/detail/exceptions.hpp>
|
||||
#include <nlohmann/detail/hash.hpp>
|
||||
#include <nlohmann/detail/input/binary_reader.hpp>
|
||||
#include <nlohmann/detail/input/input_adapters.hpp>
|
||||
#include <nlohmann/detail/input/json_sax.hpp>
|
||||
#include <nlohmann/detail/input/lexer.hpp>
|
||||
#include <nlohmann/detail/input/parser.hpp>
|
||||
#include <nlohmann/detail/iterators/internal_iterator.hpp>
|
||||
@@ -62,6 +73,7 @@
|
||||
#include <nlohmann/detail/json_pointer.hpp>
|
||||
#include <nlohmann/detail/json_ref.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta/detected.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
#include <nlohmann/detail/string_escape.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
@@ -73,7 +85,8 @@
|
||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include <nlohmann/ordered_map.hpp>
|
||||
#include <nlohmann/ordered_map.hpp> // IWYU pragma: keep
|
||||
#include <nlohmann/thirdparty/hedley/hedley.hpp>
|
||||
|
||||
#if defined(JSON_HAS_CPP_17)
|
||||
#if JSON_HAS_STATIC_RTTI
|
||||
@@ -151,6 +164,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
friend class ::nlohmann::detail::json_sax_dom_parser;
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
friend class ::nlohmann::detail::json_sax_dom_callback_parser;
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
friend struct ::nlohmann::detail::diagnostic_positions;
|
||||
#endif
|
||||
friend class ::nlohmann::detail::exception;
|
||||
|
||||
/// workaround type for MSVC
|
||||
@@ -4402,25 +4418,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/update/
|
||||
void update(const_reference j, bool merge_objects = false)
|
||||
{
|
||||
update(j.begin(), j.end(), merge_objects);
|
||||
prepare_update();
|
||||
|
||||
// passed value must be an object (checked here so a type_error names
|
||||
// j, not the copy made below)
|
||||
if (JSON_HEDLEY_UNLIKELY(!j.is_object()))
|
||||
{
|
||||
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", j.type_name()), &j));
|
||||
}
|
||||
|
||||
// copy first: j may be *this or one of its descendants, and is
|
||||
// iterated (and moved from) below to update *this
|
||||
basic_json source = j;
|
||||
update_from(source, merge_objects);
|
||||
}
|
||||
|
||||
/// @brief updates a JSON object from another object, overwriting existing keys
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/update/
|
||||
void update(const_iterator first, const_iterator last, bool merge_objects = false) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
// implicitly convert a null value to an empty object
|
||||
if (is_null())
|
||||
{
|
||||
m_data.m_type = value_t::object;
|
||||
m_data.m_value.object = create<object_t>();
|
||||
assert_invariant();
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_object()))
|
||||
{
|
||||
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", type_name()), this));
|
||||
}
|
||||
prepare_update();
|
||||
|
||||
// check if range iterators belong to the same JSON object
|
||||
if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object))
|
||||
@@ -4434,7 +4451,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", first.m_object->type_name()), first.m_object));
|
||||
}
|
||||
|
||||
update_members(first, last, merge_objects, 0);
|
||||
// copy first: the range may belong to *this or one of its
|
||||
// descendants, and is iterated (and moved from) below to update
|
||||
// *this
|
||||
basic_json source(first, last);
|
||||
update_from(source, merge_objects);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -4442,15 +4463,42 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// merge_patch_iteratively is merging into, and the members still to merge
|
||||
struct merge_frame
|
||||
{
|
||||
merge_frame(basic_json* target_, const_iterator position_, const_iterator last_) noexcept
|
||||
merge_frame(basic_json* target_, iterator position_, iterator last_) noexcept
|
||||
: target(target_), position(std::move(position_)), last(std::move(last_))
|
||||
{}
|
||||
|
||||
basic_json* target;
|
||||
const_iterator position;
|
||||
const_iterator last;
|
||||
iterator position;
|
||||
iterator last;
|
||||
};
|
||||
|
||||
/// @brief converts a null value to an empty object and checks that this
|
||||
/// value is an object; called first by both @ref update overloads
|
||||
void prepare_update()
|
||||
{
|
||||
// implicitly convert a null value to an empty object; create the
|
||||
// object before setting the type, so a throwing allocation leaves
|
||||
// this value null
|
||||
if (is_null())
|
||||
{
|
||||
m_data.m_value.object = create<object_t>();
|
||||
m_data.m_type = value_t::object;
|
||||
assert_invariant();
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_object()))
|
||||
{
|
||||
JSON_THROW(type_error::create(312, detail::concat("cannot use update() with ", type_name()), this));
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief starts the @ref update_members loop over an already-copied @a
|
||||
/// source; called by both @ref update overloads
|
||||
void update_from(basic_json& source, const bool merge_objects)
|
||||
{
|
||||
update_members(source.begin(), source.end(), merge_objects, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the members loop of @ref update, for this object and range
|
||||
|
||||
@@ -4463,7 +4511,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
@param[in] depth nesting level of this object, counted from the object
|
||||
@ref update was called on
|
||||
*/
|
||||
void update_members(const const_iterator& first, const const_iterator& last, const bool merge_objects, const std::size_t depth)
|
||||
void update_members(const iterator& first, const iterator& last, const bool merge_objects, const std::size_t depth)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||
{
|
||||
@@ -4481,13 +4529,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
// are overwritten as usual" behavior (see #5402).
|
||||
if (it2 != m_data.m_value.object->end() && it2->second.is_object())
|
||||
{
|
||||
it2->second.update_members(it.value().cbegin(), it.value().cend(), true, depth + 1);
|
||||
it2->second.update_members(it.value().begin(), it.value().end(), true, depth + 1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// set_parent() also repairs the other members, which ordered_json
|
||||
// relocates when adding a key makes its vector grow
|
||||
set_parent(m_data.m_value.object->operator[](it.key()) = it.value());
|
||||
set_parent(m_data.m_value.object->operator[](it.key()) = std::move(it.value()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4501,7 +4549,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
version. Only reached for values nested deeper than @ref
|
||||
detail::recursion_depth_limit.
|
||||
*/
|
||||
void update_members_iteratively(const_iterator first, const_iterator last)
|
||||
void update_members_iteratively(iterator first, iterator last)
|
||||
{
|
||||
std::vector<merge_frame> stack;
|
||||
|
||||
@@ -4528,18 +4576,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
const auto it2 = target->m_data.m_value.object->find(first.key());
|
||||
if (it2 != target->m_data.m_value.object->end() && it2->second.is_object())
|
||||
{
|
||||
const basic_json& source = first.value();
|
||||
basic_json& source = first.value();
|
||||
++first;
|
||||
stack.emplace_back(target, first, last);
|
||||
target = &it2->second;
|
||||
first = source.cbegin();
|
||||
last = source.cend();
|
||||
first = source.begin();
|
||||
last = source.end();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// set_parent() also repairs the other members, which ordered_json
|
||||
// relocates when adding a key makes its vector grow
|
||||
target->set_parent(target->m_data.m_value.object->operator[](first.key()) = first.value());
|
||||
target->set_parent(target->m_data.m_value.object->operator[](first.key()) = std::move(first.value()));
|
||||
++first;
|
||||
}
|
||||
}
|
||||
@@ -6291,21 +6339,256 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
{
|
||||
// the patch
|
||||
basic_json result(value_t::array);
|
||||
diff_recursively(result, source, target, path, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
// if the values are the same, return an empty patch
|
||||
private:
|
||||
/// @brief two arrays or two objects @ref diff_iteratively is diffing
|
||||
struct diff_frame
|
||||
{
|
||||
diff_frame(const basic_json* source_, const basic_json* target_, const std::size_t path_length_) noexcept
|
||||
: source(source_), target(target_), path_length(path_length_)
|
||||
{}
|
||||
|
||||
// declared for GCC's -Weffc++, which asks for them in a class with
|
||||
// pointer members and a non-trivial destructor; the exception
|
||||
// specifications are left implicit, as GCC 4.8 rejects explicit ones
|
||||
// that differ from them
|
||||
diff_frame(const diff_frame&) = default;
|
||||
diff_frame(diff_frame&&) = default;
|
||||
diff_frame& operator=(const diff_frame&) = default;
|
||||
diff_frame& operator=(diff_frame&&) = default;
|
||||
~diff_frame() = default;
|
||||
|
||||
/// the values being diffed, both arrays or both objects
|
||||
const basic_json* source;
|
||||
const basic_json* target;
|
||||
/// the length of their path in `current_path`
|
||||
std::size_t path_length;
|
||||
/// arrays: the next index to diff
|
||||
std::size_t index = 0;
|
||||
/// objects: the next member of source to look at
|
||||
const_iterator member{}; // NOLINT(readability-redundant-member-init)
|
||||
/// objects: the keys common to both, in source's order
|
||||
std::vector<typename object_t::key_type> common_keys{}; // NOLINT(readability-redundant-member-init)
|
||||
/// objects: the next entry of common_keys
|
||||
std::size_t next_common = 0;
|
||||
/// objects: the "add" operations for keys only target has
|
||||
basic_json added_ops{}; // NOLINT(readability-redundant-member-init)
|
||||
};
|
||||
|
||||
// The operations of a diff are built by the functions below rather than
|
||||
// where they are needed: building one takes several temporaries, and
|
||||
// unoptimized builds give each temporary a stack slot of its own in the
|
||||
// function it appears in. In diff_recursively, which is on the call stack
|
||||
// once per nesting level, that made every level cost kilobytes of stack.
|
||||
|
||||
/// @brief append a "replace" operation for @a path with @a value to @a result
|
||||
static void diff_replace(basic_json& result, const string_t& path, const basic_json& value)
|
||||
{
|
||||
result.push_back(
|
||||
{
|
||||
{"op", "replace"}, {"path", path}, {"value", value}
|
||||
});
|
||||
}
|
||||
|
||||
/// @brief append a "remove" operation for @a path to @a result
|
||||
static void diff_remove(basic_json& result, const string_t& path)
|
||||
{
|
||||
result.push_back(object(
|
||||
{
|
||||
{"op", "remove"}, {"path", path}
|
||||
}));
|
||||
}
|
||||
|
||||
/// @brief append an "add" operation for @a path with @a value to @a result
|
||||
static void diff_add(basic_json& result, const string_t& path, const basic_json& value)
|
||||
{
|
||||
result.push_back(
|
||||
{
|
||||
{"op", "add"}, {"path", path}, {"value", value}
|
||||
});
|
||||
}
|
||||
|
||||
/// @brief append the "remove" operations for the elements of array
|
||||
/// @a source from @a index on, and the "add" operations for the
|
||||
/// elements of array @a target from source's size on, to @a result
|
||||
static void diff_array_tails(basic_json& result, const basic_json& source, const basic_json& target,
|
||||
const string_t& path, const std::size_t index)
|
||||
{
|
||||
// remove my remaining elements, highest index first; appending
|
||||
// in that order avoids the quadratic reinsertion done before
|
||||
for (std::size_t j = source.size(); j > index; --j)
|
||||
{
|
||||
diff_remove(result, detail::concat<string_t>(path, '/', detail::to_string<string_t>(j - 1)));
|
||||
}
|
||||
|
||||
// add other remaining elements
|
||||
for (std::size_t i = source.size(); i < target.size(); ++i)
|
||||
{
|
||||
diff_add(result, detail::concat<string_t>(path, "/-"), target[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief compare the keys of objects @a source and @a target
|
||||
|
||||
If object_t does not keep its members in insertion order, or if the keys
|
||||
both objects have are in the same order in both, and the keys only
|
||||
@a target has come after them, stores the keys common to both in
|
||||
source's order in @a common_keys, stores the "add" operations for the keys
|
||||
only @a target has in @a added_ops, and returns true: the caller then diffs
|
||||
the objects member by member. Otherwise, appends operations that remove
|
||||
every member of @a source and add every member of @a target to @a result,
|
||||
and returns false.
|
||||
*/
|
||||
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
|
||||
const string_t& path, std::vector<typename object_t::key_type>& common_keys,
|
||||
basic_json& added_ops)
|
||||
{
|
||||
// first pass: record, for every source key, whether it is
|
||||
// common to both objects (in source's iteration order) or
|
||||
// was deleted (i.e., in source but not in target) -- this is
|
||||
// a by-product of the target.find() call already needed to
|
||||
// tell the two cases apart, so it adds no extra lookups. The
|
||||
// "remove" ops themselves are emitted later, interleaved
|
||||
// with the per-key diffs in the caller's fast path, to match
|
||||
// source's original iteration order (as the original,
|
||||
// pre-reordering-aware implementation did) instead of
|
||||
// grouping all removes before all per-key diffs.
|
||||
std::vector<typename object_t::key_type> common_keys_source_order;
|
||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||
{
|
||||
if (target.find(it.key()) != target.end())
|
||||
{
|
||||
common_keys_source_order.push_back(it.key());
|
||||
}
|
||||
}
|
||||
|
||||
// second pass: find keys that were added (i.e., in target but
|
||||
// not in source), and record the keys common to both, in
|
||||
// target's iteration order -- again a by-product of the
|
||||
// source.find() call already needed to detect added keys. At
|
||||
// the same time, determine whether every added key comes
|
||||
// after every common key in target's order (a precondition
|
||||
// for the fast path, which only ever appends new keys
|
||||
// at the very end). Both are only needed for an object_t that
|
||||
// keeps its members in insertion order, such as the one
|
||||
// backing `ordered_json`; for any other object_t, the fast
|
||||
// path is always taken and they are not computed.
|
||||
// The patch ops for keys that were added (i.e., in target but not
|
||||
// in source) are built here so the fast path can reuse
|
||||
// them without a second source.find() per target key. Only
|
||||
// used by the fast path -- the slow (reordering) path
|
||||
// rebuilds "add" ops for every key itself.
|
||||
std::vector<typename object_t::key_type> common_keys_target_order;
|
||||
bool new_keys_form_suffix = true;
|
||||
bool seen_new_key = false;
|
||||
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
||||
{
|
||||
if (source.find(it.key()) == source.end())
|
||||
{
|
||||
seen_new_key = true;
|
||||
diff_add(added_ops, detail::concat<string_t>(path, '/', detail::escape(it.key())), it.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||
#pragma warning(push )
|
||||
#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr
|
||||
#endif
|
||||
if (detail::is_ordered_map<object_t>::value)
|
||||
{
|
||||
common_keys_target_order.push_back(it.key());
|
||||
if (seen_new_key)
|
||||
{
|
||||
new_keys_form_suffix = false;
|
||||
}
|
||||
}
|
||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||
#pragma warning( pop )
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Only an object type that keeps its members in insertion
|
||||
// order, such as nlohmann::ordered_map, can need reordering:
|
||||
// patch() appends a new member at the end of such an object.
|
||||
// Any other object type places its members itself - std::map
|
||||
// in key order, a hash map in an order its operator== ignores -
|
||||
// so a member-by-member diff always reproduces target there.
|
||||
if (!detail::is_ordered_map<object_t>::value
|
||||
|| (common_keys_source_order == common_keys_target_order && new_keys_form_suffix))
|
||||
{
|
||||
// fast path: order of common keys already matches (or the
|
||||
// object_t's iteration order does not depend on
|
||||
// insertion history), so a plain per-key diff is correct
|
||||
// and minimal, as before
|
||||
common_keys = std::move(common_keys_source_order);
|
||||
return true;
|
||||
}
|
||||
|
||||
// slow path: the common keys are in a different relative
|
||||
// order in source and target (only possible for a
|
||||
// reorderable object_t like ordered_map). Building a
|
||||
// minimal reordering patch is a nontrivial (LCS-like)
|
||||
// problem; instead, remove every source key -- both
|
||||
// deleted keys (which must be removed regardless) and
|
||||
// common keys (removed so they can be re-added in
|
||||
// target's order) -- and re-add every key that should
|
||||
// remain, with its final target value, in target's
|
||||
// order. basic_json::patch()'s "add" operation on an
|
||||
// object uses operator[], which appends at the end for a
|
||||
// vector-backed insertion-ordered map when the key does
|
||||
// not already exist -- so removing a key and then adding
|
||||
// it moves it to the end, fixing its position.
|
||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||
{
|
||||
diff_remove(result, detail::concat<string_t>(path, '/', detail::escape(it.key())));
|
||||
}
|
||||
|
||||
// add every key that is either common (just removed
|
||||
// above) or brand new, in target's iteration order, so
|
||||
// that the final order after applying the patch matches
|
||||
// target exactly
|
||||
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
||||
{
|
||||
diff_add(result, detail::concat<string_t>(path, '/', detail::escape(it.key())), it.value());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief @ref diff, for values at nesting level @a depth, appending the
|
||||
operations to @a result
|
||||
|
||||
Diffing two arrays or objects calls this function again, once per nesting
|
||||
level, so values nested deeply enough used to exhaust the call stack and
|
||||
terminate the process. The descent is bounded here: once @ref
|
||||
detail::recursion_depth_limit levels have been entered, @ref
|
||||
diff_iteratively diffs what is left without the call stack.
|
||||
*/
|
||||
static void diff_recursively(basic_json& result, const basic_json& source, const basic_json& target,
|
||||
const string_t& path, const std::size_t depth)
|
||||
{
|
||||
// if the values are the same, there is nothing to do
|
||||
if (source == target)
|
||||
{
|
||||
return result;
|
||||
return;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||
{
|
||||
diff_iteratively(result, source, target, path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (source.type() != target.type())
|
||||
{
|
||||
// different types: replace value
|
||||
result.push_back(
|
||||
{
|
||||
{"op", "replace"}, {"path", path}, {"value", target}
|
||||
});
|
||||
return result;
|
||||
diff_replace(result, path, target);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (source.type())
|
||||
@@ -6317,200 +6600,50 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
while (i < source.size() && i < target.size())
|
||||
{
|
||||
// recursive call to compare array values at index i
|
||||
auto temp_diff = diff(source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)));
|
||||
result.insert(result.end(), temp_diff.begin(), temp_diff.end());
|
||||
diff_recursively(result, source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)), depth + 1);
|
||||
++i;
|
||||
}
|
||||
|
||||
// We now reached the end of at least one array
|
||||
// in a second pass, traverse the remaining elements
|
||||
|
||||
// remove my remaining elements, highest index first; appending
|
||||
// in that order avoids the quadratic reinsertion done before
|
||||
for (std::size_t j = source.size(); j > i; --j)
|
||||
{
|
||||
result.push_back(object(
|
||||
{
|
||||
{"op", "remove"},
|
||||
{"path", detail::concat<string_t>(path, '/', detail::to_string<string_t>(j - 1))}
|
||||
}));
|
||||
}
|
||||
i = source.size();
|
||||
|
||||
// add other remaining elements
|
||||
while (i < target.size())
|
||||
{
|
||||
result.push_back(
|
||||
{
|
||||
{"op", "add"},
|
||||
{"path", detail::concat<string_t>(path, "/-")},
|
||||
{"value", target[i]}
|
||||
});
|
||||
++i;
|
||||
}
|
||||
|
||||
diff_array_tails(result, source, target, path, i);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
// first pass: record, for every source key, whether it is
|
||||
// common to both objects (in source's iteration order) or
|
||||
// was deleted (i.e., in source but not in target) -- this is
|
||||
// a by-product of the target.find() call already needed to
|
||||
// tell the two cases apart, so it adds no extra lookups. The
|
||||
// "remove" ops themselves are emitted later, interleaved
|
||||
// with the recursive per-key diffs in the fast path below,
|
||||
// to match source's original iteration order (as the
|
||||
// original, pre-reordering-aware implementation did) instead
|
||||
// of grouping all removes before all recursive diffs.
|
||||
std::vector<typename object_t::key_type> common_keys_source_order;
|
||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||
{
|
||||
if (target.find(it.key()) != target.end())
|
||||
{
|
||||
common_keys_source_order.push_back(it.key());
|
||||
}
|
||||
}
|
||||
|
||||
// second pass: find keys that were added (i.e., in target but
|
||||
// not in source), and record the keys common to both, in
|
||||
// target's iteration order -- again a by-product of the
|
||||
// source.find() call already needed to detect added keys. At
|
||||
// the same time, determine whether every added key comes
|
||||
// after every common key in target's order (a precondition
|
||||
// for the fast path below, which only ever appends new keys
|
||||
// at the very end). Both are only needed for an object_t that
|
||||
// keeps its members in insertion order, such as the one
|
||||
// backing `ordered_json`; for any other object_t, the fast
|
||||
// path is always taken and they are not computed.
|
||||
// patch ops for keys that were added (i.e., in target but not
|
||||
// in source); built here so the fast path below can reuse
|
||||
// them without a second source.find() per target key. Only
|
||||
// used by the fast path -- the slow (reordering) path
|
||||
// rebuilds "add" ops for every key itself.
|
||||
std::vector<typename object_t::key_type> common_keys_target_order;
|
||||
std::vector<typename object_t::key_type> common_keys;
|
||||
basic_json added_ops(value_t::array);
|
||||
bool new_keys_form_suffix = true;
|
||||
bool seen_new_key = false;
|
||||
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
||||
if (diff_object_keys(result, source, target, path, common_keys, added_ops))
|
||||
{
|
||||
if (source.find(it.key()) == source.end())
|
||||
{
|
||||
seen_new_key = true;
|
||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||
added_ops.push_back(
|
||||
{
|
||||
{"op", "add"}, {"path", path_key},
|
||||
{"value", it.value()}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||
#pragma warning(push )
|
||||
#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr
|
||||
#endif
|
||||
if (detail::is_ordered_map<object_t>::value)
|
||||
{
|
||||
common_keys_target_order.push_back(it.key());
|
||||
if (seen_new_key)
|
||||
{
|
||||
new_keys_form_suffix = false;
|
||||
}
|
||||
}
|
||||
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||
#pragma warning( pop )
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Only an object type that keeps its members in insertion
|
||||
// order, such as nlohmann::ordered_map, can need reordering:
|
||||
// patch() appends a new member at the end of such an object.
|
||||
// Any other object type places its members itself - std::map
|
||||
// in key order, a hash map in an order its operator== ignores -
|
||||
// so a member-by-member diff always reproduces target there.
|
||||
if (!detail::is_ordered_map<object_t>::value
|
||||
|| (common_keys_source_order == common_keys_target_order && new_keys_form_suffix))
|
||||
{
|
||||
// fast path: order of common keys already matches (or the
|
||||
// object_t's iteration order does not depend on
|
||||
// insertion history), so a plain per-key recursive diff
|
||||
// is correct and minimal, as before. common_keys_source_order
|
||||
// is, by construction, the subsequence of source's keys
|
||||
// that are common to both objects, in source's iteration
|
||||
// order -- so it can be walked in lockstep with `source`
|
||||
// using a cheap key comparison instead of another lookup.
|
||||
// Deleted keys (those source keys not in common_keys_source_order)
|
||||
// are interleaved here too, in source's original order, to
|
||||
// match the historical (pre-reordering-aware) output order.
|
||||
auto common_it = common_keys_source_order.cbegin();
|
||||
// fast path: common_keys is, by construction, the
|
||||
// subsequence of source's keys that are common to both
|
||||
// objects, in source's iteration order -- so it can be
|
||||
// walked in lockstep with `source` using a cheap key
|
||||
// comparison instead of another lookup. Deleted keys
|
||||
// (those source keys not in common_keys) are interleaved
|
||||
// here too, in source's original order, to match the
|
||||
// historical (pre-reordering-aware) output order.
|
||||
auto common_it = common_keys.cbegin();
|
||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||
{
|
||||
if (common_it != common_keys_source_order.cend() && it.key() == *common_it)
|
||||
if (common_it != common_keys.cend() && it.key() == *common_it)
|
||||
{
|
||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||
auto temp_diff = diff(it.value(), target[it.key()], path_key);
|
||||
result.insert(result.end(), temp_diff.begin(), temp_diff.end());
|
||||
diff_recursively(result, it.value(), target[it.key()], detail::concat<string_t>(path, '/', detail::escape(it.key())), depth + 1);
|
||||
++common_it;
|
||||
}
|
||||
else
|
||||
{
|
||||
// found a key that is not in target -> remove it
|
||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||
result.push_back(object(
|
||||
{
|
||||
{"op", "remove"}, {"path", path_key}
|
||||
}));
|
||||
diff_remove(result, detail::concat<string_t>(path, '/', detail::escape(it.key())));
|
||||
}
|
||||
}
|
||||
|
||||
// append the "add" ops for brand-new keys collected above
|
||||
// during the pass over target -- no second source.find()
|
||||
// per target key needed
|
||||
// append the "add" ops for brand-new keys collected by
|
||||
// diff_object_keys -- no second source.find() per target
|
||||
// key needed
|
||||
result.insert(result.end(), added_ops.begin(), added_ops.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
// slow path: the common keys are in a different relative
|
||||
// order in source and target (only possible for a
|
||||
// reorderable object_t like ordered_map). Building a
|
||||
// minimal reordering patch is a nontrivial (LCS-like)
|
||||
// problem; instead, remove every source key -- both
|
||||
// deleted keys (which must be removed regardless) and
|
||||
// common keys (removed so they can be re-added in
|
||||
// target's order) -- and re-add every key that should
|
||||
// remain, with its final target value, in target's
|
||||
// order. basic_json::patch()'s "add" operation on an
|
||||
// object uses operator[], which appends at the end for a
|
||||
// vector-backed insertion-ordered map when the key does
|
||||
// not already exist -- so removing a key and then adding
|
||||
// it moves it to the end, fixing its position.
|
||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||
{
|
||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||
result.push_back(object(
|
||||
{
|
||||
{"op", "remove"}, {"path", path_key}
|
||||
}));
|
||||
}
|
||||
|
||||
// add every key that is either common (just removed
|
||||
// above) or brand new, in target's iteration order, so
|
||||
// that the final order after applying the patch matches
|
||||
// target exactly
|
||||
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
||||
{
|
||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||
result.push_back(
|
||||
{
|
||||
{"op", "add"}, {"path", path_key},
|
||||
{"value", it.value()}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -6525,16 +6658,170 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
default:
|
||||
{
|
||||
// both primitive types: replace value
|
||||
result.push_back(
|
||||
{
|
||||
{"op", "replace"}, {"path", path}, {"value", target}
|
||||
});
|
||||
diff_replace(result, path, target);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief @ref diff without the call stack, appending the operations to
|
||||
@a result
|
||||
|
||||
Produces the same operations as @ref diff_recursively. Only reached for
|
||||
values nested more deeply than @ref detail::recursion_depth_limit.
|
||||
*/
|
||||
static void diff_iteratively(basic_json& result, const basic_json& source, const basic_json& target,
|
||||
const string_t& path)
|
||||
{
|
||||
// The arrays and objects being diffed are kept on an explicit stack,
|
||||
// and every pair of elements is still diffed completely before the
|
||||
// next one, so the operations come out in the same order as in
|
||||
// diff_recursively. The path of the values being diffed is kept in
|
||||
// one buffer that grows and shrinks with the stack, rather than in a
|
||||
// new string per level.
|
||||
std::vector<diff_frame> stack;
|
||||
string_t current_path = path;
|
||||
|
||||
// diff `s` against `t`, whose path is current_path: primitives,
|
||||
// values of different types, and objects whose members were reordered
|
||||
// are handled right away; arrays and other objects get a frame
|
||||
const auto enter = [&result, &stack, ¤t_path](const basic_json & s, const basic_json & t)
|
||||
{
|
||||
// if the values are the same, there is nothing to do. Arrays and
|
||||
// objects are not compared up front: comparing them visits
|
||||
// everything below them, so doing that at every level would take
|
||||
// quadratic time in the nesting depth - equal ones yield no
|
||||
// operations anyway.
|
||||
if ((!s.is_structured() || !t.is_structured()) && s == t)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (s.type() != t.type())
|
||||
{
|
||||
// different types: replace value
|
||||
diff_replace(result, current_path, t);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (s.type())
|
||||
{
|
||||
case value_t::array:
|
||||
{
|
||||
stack.emplace_back(&s, &t, current_path.size());
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
std::vector<typename object_t::key_type> common_keys;
|
||||
basic_json added_ops(value_t::array);
|
||||
if (diff_object_keys(result, s, t, current_path, common_keys, added_ops))
|
||||
{
|
||||
// fast path: the frame walks source in lockstep with
|
||||
// common_keys, as diff_recursively does, and appends
|
||||
// added_ops once all members are done
|
||||
stack.emplace_back(&s, &t, current_path.size());
|
||||
stack.back().member = s.cbegin();
|
||||
stack.back().common_keys = std::move(common_keys);
|
||||
stack.back().added_ops = std::move(added_ops);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
// both primitive types: replace value
|
||||
diff_replace(result, current_path, t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
enter(source, target);
|
||||
while (!stack.empty())
|
||||
{
|
||||
// the frame is copied out member by member and changed through
|
||||
// stack.back(): enter() may push a frame and the end of the loop
|
||||
// pops it, either of which would invalidate a reference to it
|
||||
const basic_json* const s = stack.back().source;
|
||||
const basic_json* const t = stack.back().target;
|
||||
const std::size_t path_length = stack.back().path_length;
|
||||
const std::size_t depth = stack.size();
|
||||
|
||||
if (s->is_array())
|
||||
{
|
||||
const auto& source_array = *s->m_data.m_value.array;
|
||||
const auto& target_array = *t->m_data.m_value.array;
|
||||
|
||||
// first pass: traverse common elements
|
||||
const std::size_t i = stack.back().index;
|
||||
if (i < source_array.size() && i < target_array.size())
|
||||
{
|
||||
++stack.back().index;
|
||||
detail::concat_into(current_path, '/', detail::to_string<string_t>(i));
|
||||
enter(source_array[i], target_array[i]);
|
||||
if (stack.size() == depth)
|
||||
{
|
||||
current_path.resize(path_length);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// We now reached the end of at least one array
|
||||
// in a second pass, traverse the remaining elements
|
||||
diff_array_tails(result, *s, *t, current_path, i);
|
||||
}
|
||||
else
|
||||
{
|
||||
const const_iterator it = stack.back().member;
|
||||
if (it != s->cend())
|
||||
{
|
||||
++stack.back().member;
|
||||
const std::size_t next_common = stack.back().next_common;
|
||||
if (next_common < stack.back().common_keys.size() && it.key() == stack.back().common_keys[next_common])
|
||||
{
|
||||
++stack.back().next_common;
|
||||
const basic_json& target_value = (*t)[it.key()];
|
||||
detail::concat_into(current_path, '/', detail::escape(it.key()));
|
||||
enter(it.value(), target_value);
|
||||
if (stack.size() == depth)
|
||||
{
|
||||
current_path.resize(path_length);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// found a key that is not in target -> remove it
|
||||
diff_remove(result, detail::concat<string_t>(current_path, '/', detail::escape(it.key())));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// append the "add" ops for brand-new keys collected when the
|
||||
// object was entered
|
||||
result.insert(result.end(), stack.back().added_ops.begin(), stack.back().added_ops.end());
|
||||
}
|
||||
|
||||
// this array or object is done: continue with the one it is in
|
||||
stack.pop_back();
|
||||
if (!stack.empty())
|
||||
{
|
||||
current_path.resize(stack.back().path_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/// @}
|
||||
|
||||
////////////////////////////////
|
||||
@@ -6548,7 +6835,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/merge_patch/
|
||||
void merge_patch(const basic_json& apply_patch)
|
||||
{
|
||||
apply_merge_patch(apply_patch, 0);
|
||||
// copy first: apply_patch may be *this or one of its descendants,
|
||||
// and is iterated (and moved from) below to patch *this
|
||||
basic_json patch = apply_patch;
|
||||
apply_merge_patch(patch, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -6561,7 +6851,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
detail::recursion_depth_limit levels have been entered, @ref
|
||||
merge_patch_iteratively applies what is left without the call stack.
|
||||
*/
|
||||
void apply_merge_patch(const basic_json& apply_patch, const std::size_t depth)
|
||||
void apply_merge_patch(basic_json& apply_patch, const std::size_t depth)
|
||||
{
|
||||
if (apply_patch.is_object())
|
||||
{
|
||||
@@ -6589,7 +6879,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
}
|
||||
else
|
||||
{
|
||||
*this = apply_patch;
|
||||
*this = std::move(apply_patch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6602,12 +6892,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
recursive version. Only reached for patches nested deeper than @ref
|
||||
detail::recursion_depth_limit.
|
||||
*/
|
||||
void merge_patch_iteratively(const basic_json& apply_patch)
|
||||
void merge_patch_iteratively(basic_json& apply_patch)
|
||||
{
|
||||
std::vector<merge_frame> stack;
|
||||
|
||||
// patch `target` with `patch`, or start patching it member by member
|
||||
const auto apply = [&stack](basic_json & target, const basic_json & patch)
|
||||
const auto apply = [&stack](basic_json & target, basic_json & patch)
|
||||
{
|
||||
if (patch.is_object())
|
||||
{
|
||||
@@ -6615,11 +6905,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
{
|
||||
target = basic_json::object();
|
||||
}
|
||||
stack.emplace_back(&target, patch.cbegin(), patch.cend());
|
||||
stack.emplace_back(&target, patch.begin(), patch.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
target = patch;
|
||||
target = std::move(patch);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6635,7 +6925,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
continue;
|
||||
}
|
||||
|
||||
const const_iterator member = frame.position;
|
||||
const iterator member = frame.position;
|
||||
++stack.back().position;
|
||||
if (member.value().is_null())
|
||||
{
|
||||
@@ -6802,7 +7092,10 @@ struct formatter<nlohmann::NLOHMANN_BASIC_JSON_TPL, char> // NOLINT(cert-dcl58-c
|
||||
|
||||
} // namespace std
|
||||
|
||||
#include <nlohmann/detail/macro_unscope.hpp>
|
||||
// keep: undoes the macros defined via detail/macro_scope.hpp at the top of this file; removing it
|
||||
// (nothing in this file *uses* a symbol from it) would leak JSON_* macros into every translation
|
||||
// unit that includes this header.
|
||||
#include <nlohmann/detail/macro_unscope.hpp> // IWYU pragma: keep
|
||||
|
||||
// End of GCC diagnostic pragmas for C++ modules support
|
||||
#if defined(__GNUC__) && !defined(__clang__) && __cplusplus >= 202002L
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
|
||||
#include <cstdint> // int64_t, uint64_t
|
||||
#include <map> // map
|
||||
#include <memory> // allocator
|
||||
#include <string> // string
|
||||
#include <string> // allocator, string
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
@@ -32,7 +31,7 @@ This serializer ignores the template arguments and uses ADL
|
||||
for serialization.
|
||||
*/
|
||||
template<typename T = void, typename SFINAE = void>
|
||||
struct adl_serializer;
|
||||
struct adl_serializer; // IWYU pragma: keep
|
||||
|
||||
/// a class to store JSON values
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/
|
||||
@@ -48,12 +47,12 @@ template<template<typename U, typename V, typename... Args> class ObjectType =
|
||||
adl_serializer,
|
||||
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
|
||||
class CustomBaseClass = void>
|
||||
class basic_json;
|
||||
class basic_json; // IWYU pragma: keep
|
||||
|
||||
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/
|
||||
template<typename RefStringType>
|
||||
class json_pointer;
|
||||
class json_pointer; // IWYU pragma: keep
|
||||
|
||||
/*!
|
||||
@brief default specialization
|
||||
@@ -64,7 +63,7 @@ using json = basic_json<>;
|
||||
/// @brief a minimal map-like container that preserves insertion order
|
||||
/// @sa https://json.nlohmann.me/api/ordered_map/
|
||||
template<class Key, class T, class IgnoredLess, class Allocator>
|
||||
struct ordered_map;
|
||||
struct ordered_map; // IWYU pragma: keep
|
||||
|
||||
/// @brief specialization that maintains the insertion order of object keys
|
||||
/// @sa https://json.nlohmann.me/api/ordered_json/
|
||||
|
||||
@@ -12,13 +12,14 @@
|
||||
#include <functional> // equal_to, less
|
||||
#include <initializer_list> // initializer_list
|
||||
#include <iterator> // input_iterator_tag, iterator_traits
|
||||
#include <memory> // allocator
|
||||
#include <new> // for operator new (placement new)
|
||||
#include <stdexcept> // for out_of_range
|
||||
#include <tuple> // forward_as_tuple
|
||||
#include <type_traits> // enable_if, integral_constant, is_convertible, is_nothrow_move_constructible
|
||||
#include <utility> // forward, move, pair, piecewise_construct
|
||||
#include <vector> // vector
|
||||
#include <vector> // vector, allocator
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user