mirror of
https://github.com/nlohmann/json.git
synced 2026-07-07 11:05:09 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| feeab08982 | |||
| ce576f6316 | |||
| 4cad4e08a4 |
@@ -12,7 +12,7 @@
|
|||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cmath> // ldexp
|
#include <cmath> // ldexp
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t
|
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy
|
||||||
#include <iterator> // back_inserter
|
#include <iterator> // back_inserter
|
||||||
@@ -2908,7 +2908,18 @@ class binary_reader
|
|||||||
const NumberType len,
|
const NumberType len,
|
||||||
string_t& result)
|
string_t& result)
|
||||||
{
|
{
|
||||||
return get_bytes(format, len, "string", result);
|
bool success = true;
|
||||||
|
for (NumberType i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
get();
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string")))
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result.push_back(static_cast<typename string_t::value_type>(current));
|
||||||
|
}
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -2930,66 +2941,18 @@ class binary_reader
|
|||||||
const NumberType len,
|
const NumberType len,
|
||||||
binary_t& result)
|
binary_t& result)
|
||||||
{
|
{
|
||||||
return get_bytes(format, len, "binary", result);
|
bool success = true;
|
||||||
}
|
for (NumberType i = 0; i < len; i++)
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief read @a len bytes from the input into a string or byte container
|
|
||||||
|
|
||||||
@tparam NumberType the type of the length
|
|
||||||
@tparam ContainerType the destination container (string_t or binary_t)
|
|
||||||
@param[in] format the current format (for diagnostics)
|
|
||||||
@param[in] len number of bytes to read
|
|
||||||
@param[in] context further context information (for diagnostics)
|
|
||||||
@param[out] result container the bytes are appended to
|
|
||||||
|
|
||||||
@return whether reading completed
|
|
||||||
|
|
||||||
@note We cannot reserve @a len bytes for the result up front, because
|
|
||||||
@a len may be far larger than the actual input. Instead we read in
|
|
||||||
bounded chunks, so the peak allocation is capped regardless of the
|
|
||||||
claimed length while the per-byte loop is replaced by block copies
|
|
||||||
(a std::memcpy for contiguous inputs). @ref unexpect_eof() still
|
|
||||||
detects a premature end of input.
|
|
||||||
*/
|
|
||||||
template<typename NumberType, typename ContainerType>
|
|
||||||
bool get_bytes(const input_format_t format,
|
|
||||||
NumberType len,
|
|
||||||
const char* context,
|
|
||||||
ContainerType& result)
|
|
||||||
{
|
{
|
||||||
// upper bound on the number of bytes read (and allocated) per chunk
|
get();
|
||||||
constexpr std::size_t chunk_size = 4096;
|
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary")))
|
||||||
|
|
||||||
while (len > 0)
|
|
||||||
{
|
{
|
||||||
// number of bytes to read this iteration: min(chunk_size, len),
|
success = false;
|
||||||
// computed without truncating chunk_size to a narrow NumberType
|
break;
|
||||||
const std::size_t wanted = (static_cast<std::uintmax_t>(len) < static_cast<std::uintmax_t>(chunk_size))
|
|
||||||
? static_cast<std::size_t>(len)
|
|
||||||
: chunk_size;
|
|
||||||
const std::size_t old_size = result.size();
|
|
||||||
result.resize(old_size + wanted);
|
|
||||||
// resize() is required to make size() exactly old_size + wanted;
|
|
||||||
// that is the room get_elements() is allowed to write into
|
|
||||||
JSON_ASSERT(result.size() == old_size + wanted);
|
|
||||||
const std::size_t bytes_read = ia.get_elements(&result[old_size], wanted);
|
|
||||||
chars_read += bytes_read;
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(bytes_read < wanted))
|
|
||||||
{
|
|
||||||
// premature end of input: shrink to what was actually read and
|
|
||||||
// report the failure at the first missing byte (same position
|
|
||||||
// accounting as get_to() for partial number reads)
|
|
||||||
result.resize(old_size + bytes_read);
|
|
||||||
++chars_read;
|
|
||||||
current = char_traits<char_type>::eof();
|
|
||||||
return unexpect_eof(format, context);
|
|
||||||
}
|
}
|
||||||
// a full chunk was read; get_elements() never returns more than requested
|
result.push_back(static_cast<typename binary_t::value_type>(current));
|
||||||
JSON_ASSERT(bytes_read == wanted);
|
|
||||||
len = static_cast<NumberType>(len - static_cast<NumberType>(wanted));
|
|
||||||
}
|
}
|
||||||
return true;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
@@ -161,18 +161,8 @@ class iterator_input_adapter
|
|||||||
public:
|
public:
|
||||||
using char_type = typename std::iterator_traits<IteratorType>::value_type;
|
using char_type = typename std::iterator_traits<IteratorType>::value_type;
|
||||||
|
|
||||||
// Whether the lexer may reconstruct already-consumed input on demand (for
|
|
||||||
// diagnostics) instead of copying every scanned character eagerly. This is
|
|
||||||
// only sound for multi-pass, randomly-addressable byte input: the iterator
|
|
||||||
// must be random-access (so the consumed prefix can be revisited in O(1))
|
|
||||||
// and each element must map 1:1 to an input byte (wide inputs are wrapped
|
|
||||||
// in wide_string_input_adapter, which does not expose this).
|
|
||||||
static constexpr bool supports_seek =
|
|
||||||
std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value
|
|
||||||
&& sizeof(char_type) == 1;
|
|
||||||
|
|
||||||
iterator_input_adapter(IteratorType first, IteratorType last)
|
iterator_input_adapter(IteratorType first, IteratorType last)
|
||||||
: begin(first), current(std::move(first)), end(std::move(last))
|
: current(std::move(first)), end(std::move(last))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
typename char_traits<char_type>::int_type get_character()
|
typename char_traits<char_type>::int_type get_character()
|
||||||
@@ -187,67 +177,9 @@ class iterator_input_adapter
|
|||||||
return char_traits<char_type>::eof();
|
return char_traits<char_type>::eof();
|
||||||
}
|
}
|
||||||
|
|
||||||
// number of characters consumed from the input so far
|
// for general iterators, we cannot really do something better than falling back to processing the range one-by-one
|
||||||
std::size_t get_consumed_count() const
|
|
||||||
{
|
|
||||||
return static_cast<std::size_t>(std::distance(begin, current));
|
|
||||||
}
|
|
||||||
|
|
||||||
// append the already-consumed characters in the half-open range
|
|
||||||
// [first_index, last_index) to @a out; only valid when supports_seek
|
|
||||||
template<typename ContainerType>
|
|
||||||
void copy_consumed_range(std::size_t first_index, std::size_t last_index, ContainerType& out) const
|
|
||||||
{
|
|
||||||
const auto from = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(first_index));
|
|
||||||
const auto to = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(last_index));
|
|
||||||
out.insert(out.end(), from, to);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy up to count * sizeof(T) bytes into dest, returning the number of
|
|
||||||
// bytes actually read. For contiguous iterators (e.g. pointers) this is a
|
|
||||||
// single std::memcpy; for general iterators we fall back to processing the
|
|
||||||
// range one-by-one.
|
|
||||||
template<class T>
|
template<class T>
|
||||||
std::size_t get_elements(T* dest, std::size_t count = 1)
|
std::size_t get_elements(T* dest, std::size_t count = 1)
|
||||||
{
|
|
||||||
return get_elements_impl(dest, count, std::integral_constant<bool, iterator_is_contiguous> {});
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
// whether IteratorType refers to a contiguous range and therefore supports
|
|
||||||
// a std::memcpy fast path (pointers always do; in C++20 we can also detect
|
|
||||||
// library iterators such as those of std::vector and std::string)
|
|
||||||
static constexpr bool iterator_is_contiguous =
|
|
||||||
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
|
|
||||||
std::contiguous_iterator<IteratorType> ||
|
|
||||||
#endif
|
|
||||||
std::is_pointer<IteratorType>::value;
|
|
||||||
|
|
||||||
// contiguous fast path: bulk copy the remaining range with std::memcpy
|
|
||||||
template<class T>
|
|
||||||
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
|
|
||||||
{
|
|
||||||
const std::size_t wanted = count * sizeof(T);
|
|
||||||
const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type);
|
|
||||||
const std::size_t copied = (std::min)(wanted, available);
|
|
||||||
if (JSON_HEDLEY_LIKELY(copied != 0))
|
|
||||||
{
|
|
||||||
// the copy must stay within both buffers: the caller-provided
|
|
||||||
// destination holds `wanted` bytes and the remaining input range
|
|
||||||
// holds `available` bytes, and `copied` is the minimum of the two
|
|
||||||
JSON_ASSERT(copied <= wanted); // does not overrun the destination
|
|
||||||
JSON_ASSERT(copied <= available); // does not read past the input end
|
|
||||||
// &*current yields the raw address for both raw pointers and
|
|
||||||
// non-pointer contiguous iterators (e.g. std::vector's iterator)
|
|
||||||
std::memcpy(dest, &*current, copied);
|
|
||||||
std::advance(current, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(copied / sizeof(char_type)));
|
|
||||||
}
|
|
||||||
return copied;
|
|
||||||
}
|
|
||||||
|
|
||||||
// general fallback: copy the range one element at a time
|
|
||||||
template<class T>
|
|
||||||
std::size_t get_elements_impl(T* dest, std::size_t count, std::false_type /*contiguous*/)
|
|
||||||
{
|
{
|
||||||
auto* ptr = reinterpret_cast<char*>(dest);
|
auto* ptr = reinterpret_cast<char*>(dest);
|
||||||
for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index)
|
for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index)
|
||||||
@@ -265,7 +197,7 @@ class iterator_input_adapter
|
|||||||
return count * sizeof(T);
|
return count * sizeof(T);
|
||||||
}
|
}
|
||||||
|
|
||||||
IteratorType begin;
|
private:
|
||||||
IteratorType current;
|
IteratorType current;
|
||||||
IteratorType end;
|
IteratorType end;
|
||||||
|
|
||||||
|
|||||||
@@ -103,28 +103,6 @@ class lexer_base
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Detect whether an input adapter can reconstruct already-consumed input on
|
|
||||||
// demand (see iterator_input_adapter::supports_seek). Adapters that do not
|
|
||||||
// expose the flag - e.g. file, stream, wide-string, and user-defined adapters -
|
|
||||||
// are treated as non-seekable streaming input, for which the lexer keeps
|
|
||||||
// copying every scanned character eagerly. The value is read via tag dispatch
|
|
||||||
// on is_detected so the flag is only referenced for adapters that provide it.
|
|
||||||
template<typename InputAdapterType>
|
|
||||||
using detect_supports_seek = decltype(InputAdapterType::supports_seek);
|
|
||||||
|
|
||||||
template<typename InputAdapterType>
|
|
||||||
constexpr bool input_adapter_supports_seek(std::true_type /*detected*/)
|
|
||||||
{
|
|
||||||
return InputAdapterType::supports_seek;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename InputAdapterType>
|
|
||||||
constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief lexical analysis
|
@brief lexical analysis
|
||||||
|
|
||||||
@@ -140,12 +118,6 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
using char_type = typename InputAdapterType::char_type;
|
using char_type = typename InputAdapterType::char_type;
|
||||||
using char_int_type = typename char_traits<char_type>::int_type;
|
using char_int_type = typename char_traits<char_type>::int_type;
|
||||||
|
|
||||||
/// whether the last read token can be reconstructed from the input adapter
|
|
||||||
/// on demand (in error paths) instead of being copied on every scanned
|
|
||||||
/// character; see input_adapter_supports_seek
|
|
||||||
static constexpr bool lazy_token_string =
|
|
||||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||||
|
|
||||||
@@ -1355,24 +1327,8 @@ scan_number_done:
|
|||||||
void reset() noexcept
|
void reset() noexcept
|
||||||
{
|
{
|
||||||
token_buffer.clear();
|
token_buffer.clear();
|
||||||
decimal_point_position = std::string::npos;
|
|
||||||
|
|
||||||
note_token_start(std::integral_constant<bool, lazy_token_string> {});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// seekable adapter: remember where the current token starts so it can be
|
|
||||||
/// reconstructed from the input on error; current has already been
|
|
||||||
/// consumed, hence the -1
|
|
||||||
void note_token_start(std::true_type /*lazy*/) noexcept
|
|
||||||
{
|
|
||||||
token_string_start = ia.get_consumed_count() - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// streaming adapter: start copying the token eagerly, beginning with the
|
|
||||||
/// already-read first character
|
|
||||||
void note_token_start(std::false_type /*lazy*/) noexcept
|
|
||||||
{
|
|
||||||
token_string.clear();
|
token_string.clear();
|
||||||
|
decimal_point_position = std::string::npos;
|
||||||
token_string.push_back(char_traits<char_type>::to_char_type(current));
|
token_string.push_back(char_traits<char_type>::to_char_type(current));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1401,9 +1357,10 @@ scan_number_done:
|
|||||||
current = ia.get_character();
|
current = ia.get_character();
|
||||||
}
|
}
|
||||||
|
|
||||||
// seekable adapters reconstruct the token lazily on error (see
|
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
|
||||||
// get_token_string), so the eager per-character copy is skipped
|
{
|
||||||
capture_char(std::integral_constant<bool, lazy_token_string> {});
|
token_string.push_back(char_traits<char_type>::to_char_type(current));
|
||||||
|
}
|
||||||
|
|
||||||
if (current == '\n')
|
if (current == '\n')
|
||||||
{
|
{
|
||||||
@@ -1414,18 +1371,6 @@ scan_number_done:
|
|||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// seekable adapter: nothing to capture, the token is rebuilt on error
|
|
||||||
void capture_char(std::true_type /*lazy*/) const noexcept {}
|
|
||||||
|
|
||||||
/// streaming adapter: copy the scanned character into token_string
|
|
||||||
void capture_char(std::false_type /*lazy*/)
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
|
|
||||||
{
|
|
||||||
token_string.push_back(char_traits<char_type>::to_char_type(current));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief unget current character (read it again on next get)
|
@brief unget current character (read it again on next get)
|
||||||
|
|
||||||
@@ -1453,15 +1398,6 @@ scan_number_done:
|
|||||||
--position.chars_read_current_line;
|
--position.chars_read_current_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// seekable adapter: nothing was captured, so nothing to undo
|
|
||||||
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
|
||||||
|
|
||||||
/// streaming adapter: drop the character copied by the matching get()
|
|
||||||
void uncapture_char(std::false_type /*lazy*/)
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
|
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
|
||||||
{
|
{
|
||||||
JSON_ASSERT(!token_string.empty());
|
JSON_ASSERT(!token_string.empty());
|
||||||
@@ -1519,38 +1455,14 @@ scan_number_done:
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// seekable adapter: rebuild the last read token from the input on demand
|
|
||||||
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
|
|
||||||
{
|
|
||||||
// a pending unget of a real (non-EOF) character means that character
|
|
||||||
// was consumed from the input but is not part of the token; EOF is
|
|
||||||
// never consumed, so it must not be subtracted (mirrors unget())
|
|
||||||
const bool pending_real_unget = next_unget && current != char_traits<char_type>::eof();
|
|
||||||
const std::size_t stop = ia.get_consumed_count() - (pending_real_unget ? 1u : 0u);
|
|
||||||
if (JSON_HEDLEY_LIKELY(stop >= token_string_start))
|
|
||||||
{
|
|
||||||
ia.copy_consumed_range(token_string_start, stop, out);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// streaming adapter: the token was copied eagerly while scanning
|
|
||||||
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& /*out*/, std::false_type /*lazy*/) const
|
|
||||||
{
|
|
||||||
return token_string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// return the last read token (for errors only). Will never contain EOF
|
/// return the last read token (for errors only). Will never contain EOF
|
||||||
/// (an arbitrary value that is not a valid char value, often -1), because
|
/// (an arbitrary value that is not a valid char value, often -1), because
|
||||||
/// 255 may legitimately occur. May contain NUL, which should be escaped.
|
/// 255 may legitimately occur. May contain NUL, which should be escaped.
|
||||||
std::string get_token_string() const
|
std::string get_token_string() const
|
||||||
{
|
{
|
||||||
std::vector<char_type> reconstructed;
|
|
||||||
const std::vector<char_type>& chars = collect_token_chars(reconstructed, std::integral_constant<bool, lazy_token_string> {});
|
|
||||||
|
|
||||||
// escape control characters
|
// escape control characters
|
||||||
std::string result;
|
std::string result;
|
||||||
for (const auto c : chars)
|
for (const auto c : token_string)
|
||||||
{
|
{
|
||||||
if (static_cast<unsigned char>(c) <= '\x1F')
|
if (static_cast<unsigned char>(c) <= '\x1F')
|
||||||
{
|
{
|
||||||
@@ -1711,14 +1623,9 @@ scan_number_done:
|
|||||||
/// the start position of the current token
|
/// the start position of the current token
|
||||||
position_t position {};
|
position_t position {};
|
||||||
|
|
||||||
/// raw input token string for error messages; only populated for streaming
|
/// raw input token string (for error messages)
|
||||||
/// adapters (seekable adapters reconstruct it lazily via token_string_start)
|
|
||||||
std::vector<char_type> token_string {};
|
std::vector<char_type> token_string {};
|
||||||
|
|
||||||
/// start offset of the current token within the input, used to reconstruct
|
|
||||||
/// the last read token on error for seekable adapters (see collect_token_chars)
|
|
||||||
std::size_t token_string_start = 0;
|
|
||||||
|
|
||||||
/// buffer for variable-length tokens (numbers, strings)
|
/// buffer for variable-length tokens (numbers, strings)
|
||||||
string_t token_buffer {};
|
string_t token_buffer {};
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
#include <nlohmann/detail/abi_macros.hpp>
|
#include <nlohmann/detail/abi_macros.hpp>
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Default base class of the @ref basic_json class.
|
@brief Default base class of the @ref basic_json class.
|
||||||
@@ -23,27 +25,13 @@ of @ref basic_json do not require complex case distinctions
|
|||||||
@ref basic_json always has a base class.
|
@ref basic_json always has a base class.
|
||||||
By default, this class is used because it is empty and thus has no effect
|
By default, this class is used because it is empty and thus has no effect
|
||||||
on the behavior of @ref basic_json.
|
on the behavior of @ref basic_json.
|
||||||
|
|
||||||
@note This class intentionally lives in namespace @ref nlohmann rather than
|
|
||||||
@ref nlohmann::detail. Every @ref basic_json specialization derives from
|
|
||||||
it (via @ref detail::json_base_class) unless a custom base class is
|
|
||||||
supplied, which makes its namespace an associated namespace of
|
|
||||||
@ref basic_json for the purpose of argument-dependent lookup (ADL). If
|
|
||||||
it lived in `nlohmann::detail`, that namespace - and with it the
|
|
||||||
library's internal `to_json`/`from_json` overloads - would leak into
|
|
||||||
ADL for any unqualified `to_json`/`from_json` call a user makes
|
|
||||||
involving a @ref basic_json argument, silently shadowing the user's own
|
|
||||||
overloads in some cases.
|
|
||||||
*/
|
*/
|
||||||
struct json_default_base {};
|
struct json_default_base {};
|
||||||
|
|
||||||
namespace detail
|
|
||||||
{
|
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
using json_base_class = typename std::conditional <
|
using json_base_class = typename std::conditional <
|
||||||
std::is_same<T, void>::value,
|
std::is_same<T, void>::value,
|
||||||
::nlohmann::json_default_base,
|
json_default_base,
|
||||||
T
|
T
|
||||||
>::type;
|
>::type;
|
||||||
|
|
||||||
|
|||||||
@@ -465,18 +465,12 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (codepoint <= 0xFFFF)
|
if (codepoint <= 0xFFFF)
|
||||||
{
|
{
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
||||||
static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x",
|
|
||||||
static_cast<std::uint16_t>(codepoint)));
|
|
||||||
bytes += 6;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
||||||
static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x",
|
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
|
||||||
static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)),
|
|
||||||
static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu))));
|
|
||||||
bytes += 12;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -683,6 +677,32 @@ class serializer
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief write a lowercase "\uXXXX" escape sequence into @a string_buffer
|
||||||
|
*
|
||||||
|
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
|
||||||
|
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
|
||||||
|
* four hex digits) at position @a pos of @a string_buffer via a nibble
|
||||||
|
* lookup table, avoiding the format-string parsing and locale machinery of
|
||||||
|
* `snprintf`. Advances @a pos by the number of bytes written (6).
|
||||||
|
*
|
||||||
|
* @param[in] pos position in @a string_buffer to write at; there must
|
||||||
|
* be at least 6 bytes of headroom
|
||||||
|
* @param[in] codeunit 16-bit value to encode
|
||||||
|
*/
|
||||||
|
void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept
|
||||||
|
{
|
||||||
|
JSON_ASSERT(string_buffer.size() - pos >= 6);
|
||||||
|
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||||
|
string_buffer[pos + 0] = '\\';
|
||||||
|
string_buffer[pos + 1] = 'u';
|
||||||
|
string_buffer[pos + 2] = nibble_to_hex[(codeunit >> 12u) & 0x0Fu];
|
||||||
|
string_buffer[pos + 3] = nibble_to_hex[(codeunit >> 8u) & 0x0Fu];
|
||||||
|
string_buffer[pos + 4] = nibble_to_hex[(codeunit >> 4u) & 0x0Fu];
|
||||||
|
string_buffer[pos + 5] = nibble_to_hex[codeunit & 0x0Fu];
|
||||||
|
pos += 6;
|
||||||
|
}
|
||||||
|
|
||||||
// templates to avoid warnings about useless casts
|
// templates to avoid warnings about useless casts
|
||||||
template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>
|
template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>
|
||||||
bool is_negative_number(NumberType x)
|
bool is_negative_number(NumberType x)
|
||||||
|
|||||||
@@ -6811,7 +6811,7 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cmath> // ldexp
|
#include <cmath> // ldexp
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t
|
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy
|
||||||
#include <iterator> // back_inserter
|
#include <iterator> // back_inserter
|
||||||
@@ -6993,18 +6993,8 @@ class iterator_input_adapter
|
|||||||
public:
|
public:
|
||||||
using char_type = typename std::iterator_traits<IteratorType>::value_type;
|
using char_type = typename std::iterator_traits<IteratorType>::value_type;
|
||||||
|
|
||||||
// Whether the lexer may reconstruct already-consumed input on demand (for
|
|
||||||
// diagnostics) instead of copying every scanned character eagerly. This is
|
|
||||||
// only sound for multi-pass, randomly-addressable byte input: the iterator
|
|
||||||
// must be random-access (so the consumed prefix can be revisited in O(1))
|
|
||||||
// and each element must map 1:1 to an input byte (wide inputs are wrapped
|
|
||||||
// in wide_string_input_adapter, which does not expose this).
|
|
||||||
static constexpr bool supports_seek =
|
|
||||||
std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value
|
|
||||||
&& sizeof(char_type) == 1;
|
|
||||||
|
|
||||||
iterator_input_adapter(IteratorType first, IteratorType last)
|
iterator_input_adapter(IteratorType first, IteratorType last)
|
||||||
: begin(first), current(std::move(first)), end(std::move(last))
|
: current(std::move(first)), end(std::move(last))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
typename char_traits<char_type>::int_type get_character()
|
typename char_traits<char_type>::int_type get_character()
|
||||||
@@ -7019,67 +7009,9 @@ class iterator_input_adapter
|
|||||||
return char_traits<char_type>::eof();
|
return char_traits<char_type>::eof();
|
||||||
}
|
}
|
||||||
|
|
||||||
// number of characters consumed from the input so far
|
// for general iterators, we cannot really do something better than falling back to processing the range one-by-one
|
||||||
std::size_t get_consumed_count() const
|
|
||||||
{
|
|
||||||
return static_cast<std::size_t>(std::distance(begin, current));
|
|
||||||
}
|
|
||||||
|
|
||||||
// append the already-consumed characters in the half-open range
|
|
||||||
// [first_index, last_index) to @a out; only valid when supports_seek
|
|
||||||
template<typename ContainerType>
|
|
||||||
void copy_consumed_range(std::size_t first_index, std::size_t last_index, ContainerType& out) const
|
|
||||||
{
|
|
||||||
const auto from = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(first_index));
|
|
||||||
const auto to = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(last_index));
|
|
||||||
out.insert(out.end(), from, to);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy up to count * sizeof(T) bytes into dest, returning the number of
|
|
||||||
// bytes actually read. For contiguous iterators (e.g. pointers) this is a
|
|
||||||
// single std::memcpy; for general iterators we fall back to processing the
|
|
||||||
// range one-by-one.
|
|
||||||
template<class T>
|
template<class T>
|
||||||
std::size_t get_elements(T* dest, std::size_t count = 1)
|
std::size_t get_elements(T* dest, std::size_t count = 1)
|
||||||
{
|
|
||||||
return get_elements_impl(dest, count, std::integral_constant<bool, iterator_is_contiguous> {});
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
// whether IteratorType refers to a contiguous range and therefore supports
|
|
||||||
// a std::memcpy fast path (pointers always do; in C++20 we can also detect
|
|
||||||
// library iterators such as those of std::vector and std::string)
|
|
||||||
static constexpr bool iterator_is_contiguous =
|
|
||||||
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
|
|
||||||
std::contiguous_iterator<IteratorType> ||
|
|
||||||
#endif
|
|
||||||
std::is_pointer<IteratorType>::value;
|
|
||||||
|
|
||||||
// contiguous fast path: bulk copy the remaining range with std::memcpy
|
|
||||||
template<class T>
|
|
||||||
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
|
|
||||||
{
|
|
||||||
const std::size_t wanted = count * sizeof(T);
|
|
||||||
const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type);
|
|
||||||
const std::size_t copied = (std::min)(wanted, available);
|
|
||||||
if (JSON_HEDLEY_LIKELY(copied != 0))
|
|
||||||
{
|
|
||||||
// the copy must stay within both buffers: the caller-provided
|
|
||||||
// destination holds `wanted` bytes and the remaining input range
|
|
||||||
// holds `available` bytes, and `copied` is the minimum of the two
|
|
||||||
JSON_ASSERT(copied <= wanted); // does not overrun the destination
|
|
||||||
JSON_ASSERT(copied <= available); // does not read past the input end
|
|
||||||
// &*current yields the raw address for both raw pointers and
|
|
||||||
// non-pointer contiguous iterators (e.g. std::vector's iterator)
|
|
||||||
std::memcpy(dest, &*current, copied);
|
|
||||||
std::advance(current, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(copied / sizeof(char_type)));
|
|
||||||
}
|
|
||||||
return copied;
|
|
||||||
}
|
|
||||||
|
|
||||||
// general fallback: copy the range one element at a time
|
|
||||||
template<class T>
|
|
||||||
std::size_t get_elements_impl(T* dest, std::size_t count, std::false_type /*contiguous*/)
|
|
||||||
{
|
{
|
||||||
auto* ptr = reinterpret_cast<char*>(dest);
|
auto* ptr = reinterpret_cast<char*>(dest);
|
||||||
for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index)
|
for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index)
|
||||||
@@ -7097,7 +7029,7 @@ class iterator_input_adapter
|
|||||||
return count * sizeof(T);
|
return count * sizeof(T);
|
||||||
}
|
}
|
||||||
|
|
||||||
IteratorType begin;
|
private:
|
||||||
IteratorType current;
|
IteratorType current;
|
||||||
IteratorType end;
|
IteratorType end;
|
||||||
|
|
||||||
@@ -7578,28 +7510,6 @@ class lexer_base
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Detect whether an input adapter can reconstruct already-consumed input on
|
|
||||||
// demand (see iterator_input_adapter::supports_seek). Adapters that do not
|
|
||||||
// expose the flag - e.g. file, stream, wide-string, and user-defined adapters -
|
|
||||||
// are treated as non-seekable streaming input, for which the lexer keeps
|
|
||||||
// copying every scanned character eagerly. The value is read via tag dispatch
|
|
||||||
// on is_detected so the flag is only referenced for adapters that provide it.
|
|
||||||
template<typename InputAdapterType>
|
|
||||||
using detect_supports_seek = decltype(InputAdapterType::supports_seek);
|
|
||||||
|
|
||||||
template<typename InputAdapterType>
|
|
||||||
constexpr bool input_adapter_supports_seek(std::true_type /*detected*/)
|
|
||||||
{
|
|
||||||
return InputAdapterType::supports_seek;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename InputAdapterType>
|
|
||||||
constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief lexical analysis
|
@brief lexical analysis
|
||||||
|
|
||||||
@@ -7615,12 +7525,6 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
using char_type = typename InputAdapterType::char_type;
|
using char_type = typename InputAdapterType::char_type;
|
||||||
using char_int_type = typename char_traits<char_type>::int_type;
|
using char_int_type = typename char_traits<char_type>::int_type;
|
||||||
|
|
||||||
/// whether the last read token can be reconstructed from the input adapter
|
|
||||||
/// on demand (in error paths) instead of being copied on every scanned
|
|
||||||
/// character; see input_adapter_supports_seek
|
|
||||||
static constexpr bool lazy_token_string =
|
|
||||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||||
|
|
||||||
@@ -8830,24 +8734,8 @@ scan_number_done:
|
|||||||
void reset() noexcept
|
void reset() noexcept
|
||||||
{
|
{
|
||||||
token_buffer.clear();
|
token_buffer.clear();
|
||||||
decimal_point_position = std::string::npos;
|
|
||||||
|
|
||||||
note_token_start(std::integral_constant<bool, lazy_token_string> {});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// seekable adapter: remember where the current token starts so it can be
|
|
||||||
/// reconstructed from the input on error; current has already been
|
|
||||||
/// consumed, hence the -1
|
|
||||||
void note_token_start(std::true_type /*lazy*/) noexcept
|
|
||||||
{
|
|
||||||
token_string_start = ia.get_consumed_count() - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// streaming adapter: start copying the token eagerly, beginning with the
|
|
||||||
/// already-read first character
|
|
||||||
void note_token_start(std::false_type /*lazy*/) noexcept
|
|
||||||
{
|
|
||||||
token_string.clear();
|
token_string.clear();
|
||||||
|
decimal_point_position = std::string::npos;
|
||||||
token_string.push_back(char_traits<char_type>::to_char_type(current));
|
token_string.push_back(char_traits<char_type>::to_char_type(current));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8876,9 +8764,10 @@ scan_number_done:
|
|||||||
current = ia.get_character();
|
current = ia.get_character();
|
||||||
}
|
}
|
||||||
|
|
||||||
// seekable adapters reconstruct the token lazily on error (see
|
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
|
||||||
// get_token_string), so the eager per-character copy is skipped
|
{
|
||||||
capture_char(std::integral_constant<bool, lazy_token_string> {});
|
token_string.push_back(char_traits<char_type>::to_char_type(current));
|
||||||
|
}
|
||||||
|
|
||||||
if (current == '\n')
|
if (current == '\n')
|
||||||
{
|
{
|
||||||
@@ -8889,18 +8778,6 @@ scan_number_done:
|
|||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// seekable adapter: nothing to capture, the token is rebuilt on error
|
|
||||||
void capture_char(std::true_type /*lazy*/) const noexcept {}
|
|
||||||
|
|
||||||
/// streaming adapter: copy the scanned character into token_string
|
|
||||||
void capture_char(std::false_type /*lazy*/)
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
|
|
||||||
{
|
|
||||||
token_string.push_back(char_traits<char_type>::to_char_type(current));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief unget current character (read it again on next get)
|
@brief unget current character (read it again on next get)
|
||||||
|
|
||||||
@@ -8928,15 +8805,6 @@ scan_number_done:
|
|||||||
--position.chars_read_current_line;
|
--position.chars_read_current_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// seekable adapter: nothing was captured, so nothing to undo
|
|
||||||
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
|
||||||
|
|
||||||
/// streaming adapter: drop the character copied by the matching get()
|
|
||||||
void uncapture_char(std::false_type /*lazy*/)
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
|
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
|
||||||
{
|
{
|
||||||
JSON_ASSERT(!token_string.empty());
|
JSON_ASSERT(!token_string.empty());
|
||||||
@@ -8994,38 +8862,14 @@ scan_number_done:
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// seekable adapter: rebuild the last read token from the input on demand
|
|
||||||
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
|
|
||||||
{
|
|
||||||
// a pending unget of a real (non-EOF) character means that character
|
|
||||||
// was consumed from the input but is not part of the token; EOF is
|
|
||||||
// never consumed, so it must not be subtracted (mirrors unget())
|
|
||||||
const bool pending_real_unget = next_unget && current != char_traits<char_type>::eof();
|
|
||||||
const std::size_t stop = ia.get_consumed_count() - (pending_real_unget ? 1u : 0u);
|
|
||||||
if (JSON_HEDLEY_LIKELY(stop >= token_string_start))
|
|
||||||
{
|
|
||||||
ia.copy_consumed_range(token_string_start, stop, out);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// streaming adapter: the token was copied eagerly while scanning
|
|
||||||
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& /*out*/, std::false_type /*lazy*/) const
|
|
||||||
{
|
|
||||||
return token_string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// return the last read token (for errors only). Will never contain EOF
|
/// return the last read token (for errors only). Will never contain EOF
|
||||||
/// (an arbitrary value that is not a valid char value, often -1), because
|
/// (an arbitrary value that is not a valid char value, often -1), because
|
||||||
/// 255 may legitimately occur. May contain NUL, which should be escaped.
|
/// 255 may legitimately occur. May contain NUL, which should be escaped.
|
||||||
std::string get_token_string() const
|
std::string get_token_string() const
|
||||||
{
|
{
|
||||||
std::vector<char_type> reconstructed;
|
|
||||||
const std::vector<char_type>& chars = collect_token_chars(reconstructed, std::integral_constant<bool, lazy_token_string> {});
|
|
||||||
|
|
||||||
// escape control characters
|
// escape control characters
|
||||||
std::string result;
|
std::string result;
|
||||||
for (const auto c : chars)
|
for (const auto c : token_string)
|
||||||
{
|
{
|
||||||
if (static_cast<unsigned char>(c) <= '\x1F')
|
if (static_cast<unsigned char>(c) <= '\x1F')
|
||||||
{
|
{
|
||||||
@@ -9186,14 +9030,9 @@ scan_number_done:
|
|||||||
/// the start position of the current token
|
/// the start position of the current token
|
||||||
position_t position {};
|
position_t position {};
|
||||||
|
|
||||||
/// raw input token string for error messages; only populated for streaming
|
/// raw input token string (for error messages)
|
||||||
/// adapters (seekable adapters reconstruct it lazily via token_string_start)
|
|
||||||
std::vector<char_type> token_string {};
|
std::vector<char_type> token_string {};
|
||||||
|
|
||||||
/// start offset of the current token within the input, used to reconstruct
|
|
||||||
/// the last read token on error for seekable adapters (see collect_token_chars)
|
|
||||||
std::size_t token_string_start = 0;
|
|
||||||
|
|
||||||
/// buffer for variable-length tokens (numbers, strings)
|
/// buffer for variable-length tokens (numbers, strings)
|
||||||
string_t token_buffer {};
|
string_t token_buffer {};
|
||||||
|
|
||||||
@@ -13234,7 +13073,18 @@ class binary_reader
|
|||||||
const NumberType len,
|
const NumberType len,
|
||||||
string_t& result)
|
string_t& result)
|
||||||
{
|
{
|
||||||
return get_bytes(format, len, "string", result);
|
bool success = true;
|
||||||
|
for (NumberType i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
get();
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string")))
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result.push_back(static_cast<typename string_t::value_type>(current));
|
||||||
|
}
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -13256,66 +13106,18 @@ class binary_reader
|
|||||||
const NumberType len,
|
const NumberType len,
|
||||||
binary_t& result)
|
binary_t& result)
|
||||||
{
|
{
|
||||||
return get_bytes(format, len, "binary", result);
|
bool success = true;
|
||||||
}
|
for (NumberType i = 0; i < len; i++)
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief read @a len bytes from the input into a string or byte container
|
|
||||||
|
|
||||||
@tparam NumberType the type of the length
|
|
||||||
@tparam ContainerType the destination container (string_t or binary_t)
|
|
||||||
@param[in] format the current format (for diagnostics)
|
|
||||||
@param[in] len number of bytes to read
|
|
||||||
@param[in] context further context information (for diagnostics)
|
|
||||||
@param[out] result container the bytes are appended to
|
|
||||||
|
|
||||||
@return whether reading completed
|
|
||||||
|
|
||||||
@note We cannot reserve @a len bytes for the result up front, because
|
|
||||||
@a len may be far larger than the actual input. Instead we read in
|
|
||||||
bounded chunks, so the peak allocation is capped regardless of the
|
|
||||||
claimed length while the per-byte loop is replaced by block copies
|
|
||||||
(a std::memcpy for contiguous inputs). @ref unexpect_eof() still
|
|
||||||
detects a premature end of input.
|
|
||||||
*/
|
|
||||||
template<typename NumberType, typename ContainerType>
|
|
||||||
bool get_bytes(const input_format_t format,
|
|
||||||
NumberType len,
|
|
||||||
const char* context,
|
|
||||||
ContainerType& result)
|
|
||||||
{
|
{
|
||||||
// upper bound on the number of bytes read (and allocated) per chunk
|
get();
|
||||||
constexpr std::size_t chunk_size = 4096;
|
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary")))
|
||||||
|
|
||||||
while (len > 0)
|
|
||||||
{
|
{
|
||||||
// number of bytes to read this iteration: min(chunk_size, len),
|
success = false;
|
||||||
// computed without truncating chunk_size to a narrow NumberType
|
break;
|
||||||
const std::size_t wanted = (static_cast<std::uintmax_t>(len) < static_cast<std::uintmax_t>(chunk_size))
|
|
||||||
? static_cast<std::size_t>(len)
|
|
||||||
: chunk_size;
|
|
||||||
const std::size_t old_size = result.size();
|
|
||||||
result.resize(old_size + wanted);
|
|
||||||
// resize() is required to make size() exactly old_size + wanted;
|
|
||||||
// that is the room get_elements() is allowed to write into
|
|
||||||
JSON_ASSERT(result.size() == old_size + wanted);
|
|
||||||
const std::size_t bytes_read = ia.get_elements(&result[old_size], wanted);
|
|
||||||
chars_read += bytes_read;
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(bytes_read < wanted))
|
|
||||||
{
|
|
||||||
// premature end of input: shrink to what was actually read and
|
|
||||||
// report the failure at the first missing byte (same position
|
|
||||||
// accounting as get_to() for partial number reads)
|
|
||||||
result.resize(old_size + bytes_read);
|
|
||||||
++chars_read;
|
|
||||||
current = char_traits<char_type>::eof();
|
|
||||||
return unexpect_eof(format, context);
|
|
||||||
}
|
}
|
||||||
// a full chunk was read; get_elements() never returns more than requested
|
result.push_back(static_cast<typename binary_t::value_type>(current));
|
||||||
JSON_ASSERT(bytes_read == wanted);
|
|
||||||
len = static_cast<NumberType>(len - static_cast<NumberType>(wanted));
|
|
||||||
}
|
}
|
||||||
return true;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -15094,6 +14896,8 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
|
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Default base class of the @ref basic_json class.
|
@brief Default base class of the @ref basic_json class.
|
||||||
@@ -15104,27 +14908,13 @@ of @ref basic_json do not require complex case distinctions
|
|||||||
@ref basic_json always has a base class.
|
@ref basic_json always has a base class.
|
||||||
By default, this class is used because it is empty and thus has no effect
|
By default, this class is used because it is empty and thus has no effect
|
||||||
on the behavior of @ref basic_json.
|
on the behavior of @ref basic_json.
|
||||||
|
|
||||||
@note This class intentionally lives in namespace @ref nlohmann rather than
|
|
||||||
@ref nlohmann::detail. Every @ref basic_json specialization derives from
|
|
||||||
it (via @ref detail::json_base_class) unless a custom base class is
|
|
||||||
supplied, which makes its namespace an associated namespace of
|
|
||||||
@ref basic_json for the purpose of argument-dependent lookup (ADL). If
|
|
||||||
it lived in `nlohmann::detail`, that namespace - and with it the
|
|
||||||
library's internal `to_json`/`from_json` overloads - would leak into
|
|
||||||
ADL for any unqualified `to_json`/`from_json` call a user makes
|
|
||||||
involving a @ref basic_json argument, silently shadowing the user's own
|
|
||||||
overloads in some cases.
|
|
||||||
*/
|
*/
|
||||||
struct json_default_base {};
|
struct json_default_base {};
|
||||||
|
|
||||||
namespace detail
|
|
||||||
{
|
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
using json_base_class = typename std::conditional <
|
using json_base_class = typename std::conditional <
|
||||||
std::is_same<T, void>::value,
|
std::is_same<T, void>::value,
|
||||||
::nlohmann::json_default_base,
|
json_default_base,
|
||||||
T
|
T
|
||||||
>::type;
|
>::type;
|
||||||
|
|
||||||
@@ -19962,18 +19752,12 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (codepoint <= 0xFFFF)
|
if (codepoint <= 0xFFFF)
|
||||||
{
|
{
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
||||||
static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x",
|
|
||||||
static_cast<std::uint16_t>(codepoint)));
|
|
||||||
bytes += 6;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
||||||
static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x",
|
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
|
||||||
static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)),
|
|
||||||
static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu))));
|
|
||||||
bytes += 12;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -20180,6 +19964,32 @@ class serializer
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @brief write a lowercase "\uXXXX" escape sequence into @a string_buffer
|
||||||
|
*
|
||||||
|
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
|
||||||
|
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
|
||||||
|
* four hex digits) at position @a pos of @a string_buffer via a nibble
|
||||||
|
* lookup table, avoiding the format-string parsing and locale machinery of
|
||||||
|
* `snprintf`. Advances @a pos by the number of bytes written (6).
|
||||||
|
*
|
||||||
|
* @param[in] pos position in @a string_buffer to write at; there must
|
||||||
|
* be at least 6 bytes of headroom
|
||||||
|
* @param[in] codeunit 16-bit value to encode
|
||||||
|
*/
|
||||||
|
void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept
|
||||||
|
{
|
||||||
|
JSON_ASSERT(string_buffer.size() - pos >= 6);
|
||||||
|
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||||
|
string_buffer[pos + 0] = '\\';
|
||||||
|
string_buffer[pos + 1] = 'u';
|
||||||
|
string_buffer[pos + 2] = nibble_to_hex[(codeunit >> 12u) & 0x0Fu];
|
||||||
|
string_buffer[pos + 3] = nibble_to_hex[(codeunit >> 8u) & 0x0Fu];
|
||||||
|
string_buffer[pos + 4] = nibble_to_hex[(codeunit >> 4u) & 0x0Fu];
|
||||||
|
string_buffer[pos + 5] = nibble_to_hex[codeunit & 0x0Fu];
|
||||||
|
pos += 6;
|
||||||
|
}
|
||||||
|
|
||||||
// templates to avoid warnings about useless casts
|
// templates to avoid warnings about useless casts
|
||||||
template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>
|
template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>
|
||||||
bool is_negative_number(NumberType x)
|
bool is_negative_number(NumberType x)
|
||||||
|
|||||||
@@ -229,10 +229,7 @@ TEST_CASE("algorithms")
|
|||||||
{
|
{
|
||||||
json j = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", nullptr};
|
json j = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", nullptr};
|
||||||
std::partial_sort(j.begin(), j.begin() + 4, j.end());
|
std::partial_sort(j.begin(), j.begin() + 4, j.end());
|
||||||
// only the first four elements are expected to be sorted, the rest are
|
CHECK(j == json({nullptr, false, true, 3, {{"one", 1}, {"two", 2}}, 29, {1, 2, 3}, "foo", "baz", 13}));
|
||||||
// unspecified by the standard
|
|
||||||
const json expected({nullptr, false, true, 3});
|
|
||||||
CHECK(std::equal(j.begin(), j.begin() + 4, begin(expected)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2778,43 +2778,3 @@ TEST_CASE("Tagged values")
|
|||||||
CHECK(!jb["binary"].get_binary().has_subtype());
|
CHECK(!jb["binary"].get_binary().has_subtype());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("CBOR large strings and binaries (chunked reader)")
|
|
||||||
{
|
|
||||||
// The binary reader reads strings and byte arrays in bounded chunks; make
|
|
||||||
// sure roundtripping is correct for lengths around and beyond the internal
|
|
||||||
// chunk size (4096 bytes), for both vector (iterator) and pointer inputs.
|
|
||||||
for (const std::size_t len :
|
|
||||||
{
|
|
||||||
std::size_t{0}, std::size_t{1}, std::size_t{4095}, std::size_t{4096},
|
|
||||||
std::size_t{4097}, std::size_t{8192}, std::size_t{100000}
|
|
||||||
})
|
|
||||||
{
|
|
||||||
CAPTURE(len);
|
|
||||||
|
|
||||||
// text string
|
|
||||||
const json j_string = std::string(len, 'x');
|
|
||||||
const std::vector<std::uint8_t> v_string = json::to_cbor(j_string);
|
|
||||||
CHECK(json::from_cbor(v_string) == j_string);
|
|
||||||
// pointer input exercises the std::memcpy fast path
|
|
||||||
CHECK(json::from_cbor(reinterpret_cast<const char*>(v_string.data()),
|
|
||||||
reinterpret_cast<const char*>(v_string.data()) + v_string.size()) == j_string);
|
|
||||||
|
|
||||||
// byte string
|
|
||||||
const json j_binary = json::binary(std::vector<std::uint8_t>(len, 0xCD));
|
|
||||||
const std::vector<std::uint8_t> v_binary = json::to_cbor(j_binary);
|
|
||||||
CHECK(json::from_cbor(v_binary) == j_binary);
|
|
||||||
CHECK(json::from_cbor(reinterpret_cast<const char*>(v_binary.data()),
|
|
||||||
reinterpret_cast<const char*>(v_binary.data()) + v_binary.size()) == j_binary);
|
|
||||||
|
|
||||||
// a truncated payload must still be reported as an error, never crash
|
|
||||||
// or loop, regardless of the (large) announced length
|
|
||||||
if (len > 16)
|
|
||||||
{
|
|
||||||
std::vector<std::uint8_t> truncated = v_string;
|
|
||||||
truncated.resize(truncated.size() - 8);
|
|
||||||
json _;
|
|
||||||
CHECK_THROWS_AS(_ = json::from_cbor(truncated), json::parse_error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,12 +16,6 @@ using nlohmann::json;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <valarray>
|
#include <valarray>
|
||||||
#include <algorithm>
|
|
||||||
#include <list>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -1731,110 +1725,3 @@ TEST_CASE("parser class")
|
|||||||
CHECK_THROWS_WITH_AS(_ = json::parse("/*", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid comment; missing closing '*/'; last read: '/*<U+0000>'", json::parse_error);
|
CHECK_THROWS_WITH_AS(_ = json::parse("/*", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid comment; missing closing '*/'; last read: '/*<U+0000>'", json::parse_error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this test relies on parse errors being thrown, so it is skipped when
|
|
||||||
// exceptions are disabled (json::parse aborts instead of throwing there)
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// Return the exception message from parsing @a input, or a "<no error ...>"
|
|
||||||
// sentinel if the parse unexpectedly succeeds. json::parse is nodiscard, so the
|
|
||||||
// result is consumed (via size()) to keep -Wunused-result / -Werror happy.
|
|
||||||
template<typename InputType>
|
|
||||||
std::string parse_error_message(InputType&& input)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
const json j = json::parse(std::forward<InputType>(input));
|
|
||||||
return "<no error, size " + std::to_string(j.size()) + ">";
|
|
||||||
}
|
|
||||||
catch (const json::exception& e)
|
|
||||||
{
|
|
||||||
return e.what();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename IteratorType>
|
|
||||||
std::string parse_error_message_range(IteratorType first, IteratorType last)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
const json j = json::parse(first, last);
|
|
||||||
return "<no error, size " + std::to_string(j.size()) + ">";
|
|
||||||
}
|
|
||||||
catch (const json::exception& e)
|
|
||||||
{
|
|
||||||
return e.what();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("last-read diagnostics are identical across input adapters")
|
|
||||||
{
|
|
||||||
// The lexer reconstructs the "last read" token lazily for seekable adapters
|
|
||||||
// (contiguous byte input) and copies it eagerly for streaming adapters.
|
|
||||||
// Both strategies must yield byte-for-byte identical error messages.
|
|
||||||
|
|
||||||
// a selection of malformed inputs that exercise different token kinds,
|
|
||||||
// whitespace/structural accumulation, number overflow, and control-char
|
|
||||||
// escaping in the reconstructed "last read" token
|
|
||||||
const std::vector<std::string> inputs =
|
|
||||||
{
|
|
||||||
"[1,2,x]",
|
|
||||||
" \n @",
|
|
||||||
"{\"a\": }",
|
|
||||||
"1.18973e+4932",
|
|
||||||
"\"\t\"",
|
|
||||||
"tru",
|
|
||||||
"[1 2]",
|
|
||||||
"\xEF\xBB\xBF nul",
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& s : inputs)
|
|
||||||
{
|
|
||||||
CAPTURE(s);
|
|
||||||
|
|
||||||
// reference: contiguous std::string -> seekable (lazy) path
|
|
||||||
const std::string reference = parse_error_message(s);
|
|
||||||
// every input is malformed, so parsing must fail (error messages start
|
|
||||||
// with '['; the success sentinel returned above starts with '<')
|
|
||||||
CHECK(reference.front() == '[');
|
|
||||||
|
|
||||||
// const char* -> also seekable
|
|
||||||
CHECK(parse_error_message(s.c_str()) == reference);
|
|
||||||
|
|
||||||
// std::vector<char> iterators -> seekable (random-access)
|
|
||||||
{
|
|
||||||
const std::vector<char> v(s.begin(), s.end());
|
|
||||||
CHECK(parse_error_message_range(v.begin(), v.end()) == reference);
|
|
||||||
}
|
|
||||||
|
|
||||||
// std::list iterators -> non-seekable (bidirectional) eager path
|
|
||||||
{
|
|
||||||
const std::list<char> l(s.begin(), s.end());
|
|
||||||
CHECK(parse_error_message_range(l.begin(), l.end()) == reference);
|
|
||||||
}
|
|
||||||
|
|
||||||
// std::istringstream -> non-seekable streaming eager path
|
|
||||||
{
|
|
||||||
std::istringstream ss(s);
|
|
||||||
CHECK(parse_error_message(ss) == reference);
|
|
||||||
}
|
|
||||||
|
|
||||||
// wide strings -> wide_string_input_adapter eager path; only comparable
|
|
||||||
// for ASCII input, as non-ASCII bytes are transcoded to different UTF-8
|
|
||||||
const bool is_ascii = std::all_of(s.begin(), s.end(), [](char c)
|
|
||||||
{
|
|
||||||
return static_cast<unsigned char>(c) < 0x80;
|
|
||||||
});
|
|
||||||
if (is_ascii)
|
|
||||||
{
|
|
||||||
const std::u16string w16(s.begin(), s.end());
|
|
||||||
CHECK(parse_error_message(w16) == reference);
|
|
||||||
|
|
||||||
const std::u32string w32(s.begin(), s.end());
|
|
||||||
CHECK(parse_error_message(w32) == reference);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // !defined(JSON_NOEXCEPTION)
|
|
||||||
|
|||||||
@@ -168,6 +168,32 @@ TEST_CASE("convenience functions")
|
|||||||
CHECK_THROWS_WITH_AS(check_escaped("\xC2"), "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0xC2", json::type_error&);
|
CHECK_THROWS_WITH_AS(check_escaped("\xC2"), "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0xC2", json::type_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("string escape with ensure_ascii")
|
||||||
|
{
|
||||||
|
// control characters are escaped regardless of ensure_ascii
|
||||||
|
check_escaped("\x01", "\\u0001", true);
|
||||||
|
check_escaped("\x1f", "\\u001f", true);
|
||||||
|
|
||||||
|
// non-ASCII code points in the Basic Multilingual Plane are emitted as
|
||||||
|
// a single lowercase \uXXXX escape (exercises every nibble position)
|
||||||
|
check_escaped("\xC2\x80", "\\u0080", true); // U+0080
|
||||||
|
check_escaped("\xC3\xBF", "\\u00ff", true); // U+00FF (ÿ)
|
||||||
|
check_escaped("\xDF\xBF", "\\u07ff", true); // U+07FF
|
||||||
|
check_escaped("\xE4\xBD\xA0", "\\u4f60", true); // U+4F60 (你)
|
||||||
|
check_escaped("\xEA\xAF\x8D", "\\uabcd", true); // U+ABCD
|
||||||
|
check_escaped("\xEF\xBF\xBD", "\\ufffd", true); // U+FFFD (replacement char, all-f nibbles)
|
||||||
|
|
||||||
|
// code points outside the BMP are emitted as a UTF-16 surrogate pair
|
||||||
|
// of two lowercase \uXXXX escapes
|
||||||
|
check_escaped("\xF0\x90\x80\x80", "\\ud800\\udc00", true); // U+10000 (lowest astral)
|
||||||
|
check_escaped("\xF0\x9F\x98\x80", "\\ud83d\\ude00", true); // U+1F600 (😀)
|
||||||
|
check_escaped("\xF4\x8F\xBF\xBF", "\\udbff\\udfff", true); // U+10FFFF (highest code point)
|
||||||
|
|
||||||
|
// with ensure_ascii disabled, non-ASCII input is passed through verbatim
|
||||||
|
check_escaped("\xE4\xBD\xA0", "\xE4\xBD\xA0", false);
|
||||||
|
check_escaped("\xF0\x9F\x98\x80", "\xF0\x9F\x98\x80", false);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("string concat")
|
SECTION("string concat")
|
||||||
{
|
{
|
||||||
using nlohmann::detail::concat;
|
using nlohmann::detail::concat;
|
||||||
|
|||||||
@@ -1358,100 +1358,4 @@ TEST_CASE("regression test #5122 - nlohmann::ordered_map move-assignment transfe
|
|||||||
CHECK(src.begin()->first == "after-move");
|
CHECK(src.begin()->first == "after-move");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stand-in for a third-party library (e.g., Eigen as of 3.4, which added
|
|
||||||
// STL-compatible begin()/end() to its vector types), living in its own
|
|
||||||
// namespace with its own to_json overload for its vector type.
|
|
||||||
namespace issue_4320_eigen
|
|
||||||
{
|
|
||||||
// "array-compatible" from the library's point of view (it has begin()/end()),
|
|
||||||
// but for which this (fake) third-party namespace provides its own to_json.
|
|
||||||
struct vector3
|
|
||||||
{
|
|
||||||
double v[3]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-use-default-member-init,modernize-use-default-member-init)
|
|
||||||
vector3(double x, double y, double z) : v{x, y, z} {} // NOLINT(hicpp-member-init,cppcoreguidelines-pro-type-member-init)
|
|
||||||
double x() const
|
|
||||||
{
|
|
||||||
return v[0];
|
|
||||||
}
|
|
||||||
double y() const
|
|
||||||
{
|
|
||||||
return v[1];
|
|
||||||
}
|
|
||||||
double z() const
|
|
||||||
{
|
|
||||||
return v[2];
|
|
||||||
}
|
|
||||||
double* begin()
|
|
||||||
{
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
double* end()
|
|
||||||
{
|
|
||||||
return v + 3;
|
|
||||||
}
|
|
||||||
const double* begin() const
|
|
||||||
{
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
const double* end() const
|
|
||||||
{
|
|
||||||
return v + 3;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
inline void to_json(json& j, const vector3& v) // NOLINT(misc-use-internal-linkage)
|
|
||||||
{
|
|
||||||
j = {{"x", v.x()}, {"y", v.y()}, {"z", v.z()}};
|
|
||||||
}
|
|
||||||
} // namespace issue_4320_eigen
|
|
||||||
|
|
||||||
// The user's own namespace, using the (fake) Eigen type as an implementation
|
|
||||||
// detail behind a payload type that has nothing to do with vectors/arrays.
|
|
||||||
namespace issue_4320
|
|
||||||
{
|
|
||||||
// Publicly derives from issue_4320_eigen::vector3 but does *not* define its
|
|
||||||
// own to_json - it is only ever used as a temporary to reach the base
|
|
||||||
// class's to_json via ADL.
|
|
||||||
struct vector3_wrapper : issue_4320_eigen::vector3
|
|
||||||
{
|
|
||||||
using issue_4320_eigen::vector3::vector3;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct payload
|
|
||||||
{
|
|
||||||
double x, y, z;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline vector3_wrapper to_eigen(const payload& p) // NOLINT(misc-use-internal-linkage)
|
|
||||||
{
|
|
||||||
return {p.x, p.y, p.z};
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void to_json(json& j, const payload& p) // NOLINT(misc-use-internal-linkage)
|
|
||||||
{
|
|
||||||
// Unqualified call, passing a *derived* vector3_wrapper: relies on ADL
|
|
||||||
// finding issue_4320_eigen::to_json(json&, const vector3&) through the
|
|
||||||
// vector3 base class, via a derived-to-base conversion. Must NOT resolve
|
|
||||||
// to the library's own generic array-compatible to_json (an exact-match
|
|
||||||
// template for vector3_wrapper, since it also has begin()/end()), which
|
|
||||||
// would serialize this as [x, y, z] instead of {"x":x, "y":y, "z":z}.
|
|
||||||
to_json(j, to_eigen(p));
|
|
||||||
}
|
|
||||||
} // namespace issue_4320
|
|
||||||
|
|
||||||
TEST_CASE("issue #4320 - custom base class must not leak nlohmann::detail into ADL")
|
|
||||||
{
|
|
||||||
// Before the fix, basic_json unconditionally derived from a type living in
|
|
||||||
// nlohmann::detail (json_default_base), which made nlohmann::detail an
|
|
||||||
// associated namespace of every basic_json for ADL purposes. That leaked
|
|
||||||
// the library's internal generic-array to_json overload into unqualified
|
|
||||||
// to_json() calls made from user code, silently bypassing user-defined
|
|
||||||
// to_json overloads reached via a derived-to-base conversion.
|
|
||||||
const issue_4320::payload p{1.0, 2.0, 3.0};
|
|
||||||
|
|
||||||
json j;
|
|
||||||
to_json(j, p);
|
|
||||||
CHECK(j == json({{"x", 1.0}, {"y", 2.0}, {"z", 3.0}}));
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
|
|||||||
Reference in New Issue
Block a user