mirror of
https://github.com/nlohmann/json.git
synced 2026-10-03 21:20:30 +00:00
Merge branch 'develop' into claude/todo-191-plan-508110
Resolve the conflict in docs/mkdocs/docs/home/architecture.md by taking develop's rewritten page and pointing its ordered_map links at the moved api/ordered_map/index.md. Also update the ordered_map.md links develop added in ordered_json.md, object_order.md, and template_parameters.md. Regenerate api_surface.json for the new BON8 functions and the changed comparison operator. Restrict the documented_non_public leak check to @sa URLs into json.nlohmann.me: develop now uses @sa to link GitHub issues from private members such as copy_structured, which is not a documentation leak. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -34,6 +34,18 @@
|
||||
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_PRECISE_STREAM_POSITION
|
||||
#define JSON_PRECISE_STREAM_POSITION 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_STRICT_NUL_HANDLING
|
||||
#define JSON_STRICT_NUL_HANDLING 0
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||
#else
|
||||
@@ -52,20 +64,41 @@
|
||||
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
#endif
|
||||
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS _bics
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS
|
||||
#endif
|
||||
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
#define NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION _psp
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION
|
||||
#endif
|
||||
|
||||
#if JSON_STRICT_NUL_HANDLING
|
||||
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING _snul
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING
|
||||
#endif
|
||||
|
||||
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||
#endif
|
||||
|
||||
// Construct the namespace ABI tags component
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f) json_abi ## a ## b ## c ## d ## e ## f
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f)
|
||||
|
||||
#define NLOHMANN_JSON_ABI_TAGS \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
|
||||
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
|
||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
|
||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
|
||||
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
|
||||
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING)
|
||||
|
||||
// Construct the namespace version component
|
||||
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||
|
||||
@@ -471,6 +471,30 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
|
||||
j = { std::get<Idx>(t)... };
|
||||
}
|
||||
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
// JSON_BRACE_INIT_COPY_SEMANTICS makes a one-element braced list copy its
|
||||
// element instead of wrapping it, which would serialize std::tuple<int>{5} as 5
|
||||
// rather than [5]. Build what the default deduction builds instead: an object
|
||||
// if the element is a [string, value] pair, a one-element array otherwise.
|
||||
template<typename BasicJsonType, typename Tuple>
|
||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<0> /*unused*/)
|
||||
{
|
||||
BasicJsonType element(std::get<0>(t));
|
||||
// same test as the initializer-list constructor, including the cast that
|
||||
// keeps a string type constructible from 0 from selecting operator[](key)
|
||||
const bool is_member = element.is_array() && element.size() == 2
|
||||
&& element[static_cast<typename BasicJsonType::size_type>(0)].is_string();
|
||||
if (is_member)
|
||||
{
|
||||
j = BasicJsonType::object({std::move(element)});
|
||||
}
|
||||
else
|
||||
{
|
||||
j = BasicJsonType::array({std::move(element)});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename BasicJsonType, typename Tuple>
|
||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/)
|
||||
{
|
||||
|
||||
@@ -101,7 +101,10 @@ class exception : public std::exception
|
||||
{
|
||||
if (&element.second == current)
|
||||
{
|
||||
tokens.emplace_back(element.first.c_str());
|
||||
// data() is null-terminated, so a key containing
|
||||
// a null byte is cut short here rather than
|
||||
// truncating the whole message at what()
|
||||
tokens.emplace_back(element.first.data());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstddef> // size_t
|
||||
#include <functional> // hash
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
@@ -26,6 +28,9 @@ inline std::size_t combine(std::size_t seed, std::size_t h) noexcept
|
||||
return seed;
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
std::size_t hash_iteratively(const BasicJsonType& j);
|
||||
|
||||
/*!
|
||||
@brief hash a JSON value
|
||||
|
||||
@@ -33,12 +38,21 @@ The hash function tries to rely on std::hash where possible. Furthermore, the
|
||||
type of the JSON value is taken into account to have different hash values for
|
||||
null, 0, 0U, and false, etc.
|
||||
|
||||
Hashing an array or an object hashes its elements, which used to call this
|
||||
function again once per nesting level, so a value nested deeply enough
|
||||
exhausted the call stack and terminated the process. The descent is bounded
|
||||
here: once @ref recursion_depth_limit levels have been entered, @ref
|
||||
hash_iteratively hashes what is left without the call stack. A value nested
|
||||
less deeply than that - all but a vanishing minority - is hashed exactly as
|
||||
before, without allocating.
|
||||
|
||||
@tparam BasicJsonType basic_json specialization
|
||||
@param j JSON value to hash
|
||||
@param depth nesting level of @a j, counted from the value passed by the caller
|
||||
@return hash value of j
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
std::size_t hash(const BasicJsonType& j)
|
||||
std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
|
||||
{
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
@@ -56,22 +70,32 @@ std::size_t hash(const BasicJsonType& j)
|
||||
|
||||
case BasicJsonType::value_t::object:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
return hash_iteratively(j);
|
||||
}
|
||||
|
||||
auto seed = combine(type, j.size());
|
||||
for (const auto& element : j.items())
|
||||
{
|
||||
const auto h = std::hash<string_t> {}(element.key());
|
||||
seed = combine(seed, h);
|
||||
seed = combine(seed, hash(element.value()));
|
||||
seed = combine(seed, hash(element.value(), depth + 1));
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
|
||||
case BasicJsonType::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
return hash_iteratively(j);
|
||||
}
|
||||
|
||||
auto seed = combine(type, j.size());
|
||||
for (const auto& element : j)
|
||||
{
|
||||
seed = combine(seed, hash(element));
|
||||
seed = combine(seed, hash(element, depth + 1));
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
@@ -114,7 +138,9 @@ std::size_t hash(const BasicJsonType& j)
|
||||
seed = combine(seed, static_cast<std::size_t>(j.get_binary().subtype()));
|
||||
for (const auto byte : j.get_binary())
|
||||
{
|
||||
seed = combine(seed, std::hash<std::uint8_t> {}(byte));
|
||||
// the cast is needed for binary types whose value type is not
|
||||
// an integer (e.g., std::byte)
|
||||
seed = combine(seed, std::hash<std::uint8_t> {}(static_cast<std::uint8_t>(byte)));
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
@@ -125,5 +151,77 @@ std::size_t hash(const BasicJsonType& j)
|
||||
}
|
||||
}
|
||||
|
||||
/// an array or object whose elements @ref hash_iteratively is hashing
|
||||
template<typename BasicJsonType>
|
||||
struct hash_frame
|
||||
{
|
||||
hash_frame(const BasicJsonType* value_, std::size_t seed_) noexcept
|
||||
: value(value_), position(value_->cbegin()), seed(seed_)
|
||||
{}
|
||||
|
||||
const BasicJsonType* value;
|
||||
typename BasicJsonType::const_iterator position;
|
||||
std::size_t seed;
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief hash the array or object @a j without the call stack
|
||||
|
||||
Computes the same value as @ref hash, keeping the arrays and objects it has
|
||||
entered on an explicit stack instead of descending into them. Only reached for
|
||||
values nested deeper than @ref recursion_depth_limit.
|
||||
|
||||
@tparam BasicJsonType basic_json specialization
|
||||
@param j array or object to hash
|
||||
@return hash value of j
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
std::size_t hash_iteratively(const BasicJsonType& j)
|
||||
{
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
|
||||
std::vector<hash_frame<BasicJsonType>> stack;
|
||||
stack.emplace_back(&j, combine(static_cast<std::size_t>(j.type()), j.size()));
|
||||
|
||||
while (true)
|
||||
{
|
||||
// a copy, as entering an element below can reallocate the stack; the
|
||||
// frame itself is only changed through stack.back()
|
||||
const hash_frame<BasicJsonType> frame = stack.back();
|
||||
|
||||
if (frame.position == frame.value->cend())
|
||||
{
|
||||
// all elements are hashed: fold this value's hash into its parent's
|
||||
// seed, exactly where the recursive version returns it
|
||||
const std::size_t h = frame.seed;
|
||||
stack.pop_back();
|
||||
if (stack.empty())
|
||||
{
|
||||
return h;
|
||||
}
|
||||
stack.back().seed = combine(stack.back().seed, h);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frame.value->is_object())
|
||||
{
|
||||
stack.back().seed = combine(stack.back().seed, std::hash<string_t> {}(frame.position.key()));
|
||||
}
|
||||
|
||||
// advance before entering the element, which pushes onto the stack
|
||||
const BasicJsonType& element = *frame.position;
|
||||
++stack.back().position;
|
||||
|
||||
if (element.is_structured())
|
||||
{
|
||||
stack.emplace_back(&element, combine(static_cast<std::size_t>(element.type()), element.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.back().seed = combine(stack.back().seed, hash(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
@@ -28,10 +28,12 @@
|
||||
#include <nlohmann/detail/input/input_adapters.hpp>
|
||||
#include <nlohmann/detail/input/json_sax.hpp>
|
||||
#include <nlohmann/detail/input/lexer.hpp>
|
||||
#include <nlohmann/detail/input/string_scan.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta/is_sax.hpp>
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
@@ -43,7 +45,7 @@ enum class cbor_tag_handler_t
|
||||
{
|
||||
error, ///< throw a parse_error exception in case of a tag
|
||||
ignore, ///< ignore tags
|
||||
store ///< store tags as binary type
|
||||
store ///< store tagged byte strings (for bytes 0xd8..0xdb) as binary values with the tag as subtype; other tagged values are read as if the tag were ignored
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -83,7 +85,7 @@ JSON_INLINE_VARIABLE constexpr std::size_t max_valueless_container_size = 1 << 2
|
||||
///////////////////
|
||||
|
||||
/*!
|
||||
@brief deserialization of CBOR, MessagePack, and UBJSON values
|
||||
@brief deserialization of BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values
|
||||
*/
|
||||
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType, InputAdapterType>>
|
||||
class binary_reader
|
||||
@@ -97,6 +99,11 @@ class binary_reader
|
||||
using char_type = typename InputAdapterType::char_type;
|
||||
using char_int_type = typename char_traits<char_type>::int_type;
|
||||
|
||||
/// whether the input is a contiguous block of bytes that can be inspected
|
||||
/// and consumed in bulk (as in the lexer); used by @ref get_bon8_string_bulk
|
||||
static constexpr bool bulk_scan =
|
||||
input_adapter_supports_bulk_scan<InputAdapterType>(is_detected<detect_supports_bulk_scan, InputAdapterType> {});
|
||||
|
||||
public:
|
||||
/*!
|
||||
@brief create a binary reader
|
||||
@@ -131,6 +138,7 @@ class binary_reader
|
||||
{
|
||||
sax = sax_;
|
||||
container_stack.clear();
|
||||
bon8_pushback_size = 0;
|
||||
bool result = false;
|
||||
|
||||
switch (format)
|
||||
@@ -152,6 +160,10 @@ class binary_reader
|
||||
result = parse_ubjson_internal();
|
||||
break;
|
||||
|
||||
case input_format_t::bon8:
|
||||
result = parse_bon8_internal();
|
||||
break;
|
||||
|
||||
case input_format_t::json: // LCOV_EXCL_LINE
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
@@ -164,6 +176,11 @@ class binary_reader
|
||||
{
|
||||
get_ignore_noop();
|
||||
}
|
||||
else if (input_format == input_format_t::bon8)
|
||||
{
|
||||
// a string that ends a container hands back the byte after it
|
||||
get_bon8();
|
||||
}
|
||||
else
|
||||
{
|
||||
get();
|
||||
@@ -395,6 +412,11 @@ class binary_reader
|
||||
*/
|
||||
bool get_bson_cstr(string_t& result)
|
||||
{
|
||||
if (get_bson_cstr_bulk(result, std::integral_constant<bool, bulk_scan> {}))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
auto out = std::back_inserter(result);
|
||||
while (true)
|
||||
{
|
||||
@@ -411,6 +433,46 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read a C-style string from contiguous input in one step
|
||||
|
||||
@param[in,out] result the string to append to
|
||||
@return whether the string was read; if the input has no \x00-byte, nothing
|
||||
is read, and @ref get_bson_cstr reports the end of the input
|
||||
*/
|
||||
bool get_bson_cstr_bulk(string_t& result, std::true_type /*bulk*/)
|
||||
{
|
||||
const std::size_t remaining = ia.bulk_remaining();
|
||||
if (remaining == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||
// a plain loop rather than std::memchr: most keys are short (array
|
||||
// indices are keys, too), and the call would cost more than it saves
|
||||
std::size_t length = 0;
|
||||
while (length < remaining && data[length] != 0x00)
|
||||
{
|
||||
++length;
|
||||
}
|
||||
if (length == remaining)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
result.append(reinterpret_cast<const typename string_t::value_type*>(data), length);
|
||||
// consume the string and its \x00-byte, as the byte-wise path does
|
||||
ia.bulk_skip(length + 1);
|
||||
chars_read += length + 1;
|
||||
current = 0x00;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// input that is not contiguous: C-style strings are read byte by byte
|
||||
bool get_bson_cstr_bulk(string_t& /*result*/, std::false_type /*bulk*/) const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Parses a zero-terminated string of length @a len from the BSON
|
||||
input.
|
||||
@@ -432,7 +494,21 @@ class binary_reader
|
||||
exception_message(input_format_t::bson, concat("string length must be at least 1, is ", std::to_string(len)), "string"), nullptr));
|
||||
}
|
||||
|
||||
return get_string(input_format_t::bson, len - static_cast<NumberType>(1), result) && get() != char_traits<char_type>::eof();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_string(input_format_t::bson, len - static_cast<NumberType>(1), result)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(get() != 0x00))
|
||||
{
|
||||
auto last_token = get_token_string();
|
||||
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
|
||||
exception_message(input_format_t::bson,
|
||||
"BSON string is not null-terminated",
|
||||
"string"), nullptr));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -550,8 +626,6 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////
|
||||
// CBOR //
|
||||
//////////
|
||||
@@ -579,14 +653,18 @@ class binary_reader
|
||||
input (true) or whether the last read character should
|
||||
be considered instead (false)
|
||||
@param[in] tag_handler how CBOR tags should be treated
|
||||
@param[out] tag_pending whether a tag was parsed and its value follows
|
||||
@param[out] item_read whether the tagged value's initial byte is already in current
|
||||
|
||||
@return whether a valid CBOR value was passed to the SAX parser
|
||||
*/
|
||||
bool parse_cbor_value(const bool get_char,
|
||||
const cbor_tag_handler_t tag_handler,
|
||||
bool& tag_pending)
|
||||
bool& tag_pending,
|
||||
bool& item_read)
|
||||
{
|
||||
tag_pending = false;
|
||||
item_read = false;
|
||||
|
||||
switch (get_char ? get() : current)
|
||||
{
|
||||
@@ -1008,7 +1086,17 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
get();
|
||||
return get_cbor_binary(b) && sax->binary(b);
|
||||
// a byte string (the heads accepted by get_cbor_binary) keeps the tag as subtype
|
||||
if ((current >= 0x40 && current <= 0x5B) || current == 0x5F)
|
||||
{
|
||||
return get_cbor_binary(b) && sax->binary(b);
|
||||
}
|
||||
|
||||
// not a byte string: the tagged value, whose first byte
|
||||
// was just read, is read by the caller like for ignore
|
||||
tag_pending = true;
|
||||
item_read = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
default: // LCOV_EXCL_LINE
|
||||
@@ -1490,13 +1578,14 @@ class binary_reader
|
||||
|
||||
// a tag is not a value of its own: read on until the tagged value
|
||||
bool tag_pending = false;
|
||||
bool item_read = false;
|
||||
do
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!parse_cbor_value(fetch, tag_handler, tag_pending)))
|
||||
if (JSON_HEDLEY_UNLIKELY(!parse_cbor_value(fetch, tag_handler, tag_pending, item_read)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
fetch = true;
|
||||
fetch = !item_read;
|
||||
}
|
||||
while (tag_pending);
|
||||
|
||||
@@ -3146,7 +3235,10 @@ class binary_reader
|
||||
number_string,
|
||||
out_of_range::create(406, concat("number overflow parsing '", number_string, '\''), nullptr));
|
||||
}
|
||||
return sax->number_float(parsed_float, std::move(number_string));
|
||||
// number_string is a std::string, while the SAX interface takes a
|
||||
// string_t; convert explicitly, as the two are only implicitly
|
||||
// convertible for some string types
|
||||
return sax->number_float(parsed_float, string_t(number_string.data(), number_string.size()));
|
||||
}
|
||||
case token_type::uninitialized:
|
||||
case token_type::literal_true:
|
||||
@@ -3168,6 +3260,549 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
//////////
|
||||
// BON8 //
|
||||
//////////
|
||||
|
||||
/*!
|
||||
@brief get the next byte of a BON8 value
|
||||
|
||||
A BON8 string has no length prefix and no mandatory terminator: it ends at
|
||||
the first byte that cannot continue it, which is already the first byte (or,
|
||||
for an integer that begins with a UTF-8 lead byte, the first two bytes) of
|
||||
whatever follows. The string reader hands those bytes back with
|
||||
@ref unget_bon8, and every BON8 read goes through this function so that
|
||||
they are seen again.
|
||||
|
||||
@return character read from the input
|
||||
*/
|
||||
char_int_type get_bon8()
|
||||
{
|
||||
if (bon8_pushback_size != 0)
|
||||
{
|
||||
++chars_read;
|
||||
return current = bon8_pushback[--bon8_pushback_size];
|
||||
}
|
||||
return get();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief hand a byte back so that the next @ref get_bon8 returns it again
|
||||
|
||||
@param[in] c the byte to hand back; bytes handed back are returned in
|
||||
reverse order
|
||||
*/
|
||||
void unget_bon8(const char_int_type c)
|
||||
{
|
||||
// At most two bytes are ever handed back: a byte is only handed back
|
||||
// right after it was read with get_bon8(), and the only place that
|
||||
// hands back two bytes (a lead byte and the byte after it) read both
|
||||
// of them in a row, which emptied the buffer first. This is an
|
||||
// invariant of the reader rather than a property of the input, so
|
||||
// an assertion suffices (the fuzzers are built with assertions).
|
||||
JSON_ASSERT(bon8_pushback_size < bon8_pushback.size());
|
||||
bon8_pushback[bon8_pushback_size++] = c;
|
||||
--chars_read;
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] c a byte
|
||||
@return whether @a c is a UTF-8 continuation byte (0x80..0xBF)
|
||||
*/
|
||||
static constexpr bool is_bon8_continuation(const char_int_type c) noexcept
|
||||
{
|
||||
return 0x80 <= c && c <= 0xBF;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report a parse error at the last read byte
|
||||
|
||||
@param[in] detail a detailed error message
|
||||
@param[in] context further context information
|
||||
@return false
|
||||
*/
|
||||
bool bon8_error(const std::string& detail, const char* context)
|
||||
{
|
||||
auto last_token = get_token_string();
|
||||
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
|
||||
exception_message(input_format_t::bon8, concat(detail, ": 0x", last_token), context), nullptr));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read a BON8 value and everything nested inside it
|
||||
|
||||
Reads values until the one that was begun here is complete, resuming the
|
||||
enclosing container after each element, so that the nesting depth of the
|
||||
input costs heap rather than native stack (see #5104).
|
||||
|
||||
@return whether reading the value succeeded
|
||||
*/
|
||||
bool parse_bon8_internal()
|
||||
{
|
||||
// the key currently being read; hoisted out of the loop so that its
|
||||
// capacity is reused across elements and across nesting levels
|
||||
string_t key;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!container_stack.empty())
|
||||
{
|
||||
// a copy, not a reference: it must stay valid across the
|
||||
// pop_back() below, which destroys the container_stack element
|
||||
// it would otherwise alias
|
||||
const container_frame top = container_stack.back();
|
||||
bool at_end = false;
|
||||
|
||||
if (top.remaining != npos)
|
||||
{
|
||||
// counted container (0x80..0x84, 0x86..0x8A): it ends once
|
||||
// its elements have been read
|
||||
at_end = (top.remaining == 0);
|
||||
if (!at_end)
|
||||
{
|
||||
// claim the element about to be read
|
||||
--container_stack.back().remaining;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// container 0x85 or 0x8B: it ends at an end-of-container
|
||||
// marker (0xFE); any other byte begins the next element
|
||||
at_end = (get_bon8() == 0xFE);
|
||||
if (!at_end)
|
||||
{
|
||||
unget_bon8(current);
|
||||
}
|
||||
}
|
||||
|
||||
if (at_end)
|
||||
{
|
||||
container_stack.pop_back();
|
||||
if (JSON_HEDLEY_UNLIKELY(top.is_object ? !sax->end_object() : !sax->end_array()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the value begun here is complete once its container is
|
||||
if (container_stack.empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (top.is_object)
|
||||
{
|
||||
key.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_bon8_key(key) || !sax->key(key)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!parse_bon8_value()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// a value that opened a container left it on the stack; one that
|
||||
// did not, and that was not inside a container, was the whole value
|
||||
if (container_stack.empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read one BON8 value
|
||||
|
||||
Reads a single value and passes it to the SAX parser. A value that begins
|
||||
a container is not read to its end: the container is opened with
|
||||
@ref enter_container and its elements are read by
|
||||
@ref parse_bon8_internal, so that nesting does not consume native stack.
|
||||
|
||||
@return whether reading the value succeeded
|
||||
*/
|
||||
bool parse_bon8_value()
|
||||
{
|
||||
const auto byte = get_bon8();
|
||||
|
||||
if (byte == char_traits<char_type>::eof())
|
||||
{
|
||||
return unexpect_eof(input_format_t::bon8, "value");
|
||||
}
|
||||
|
||||
// string: ASCII character
|
||||
if (byte <= 0x7F)
|
||||
{
|
||||
string_t s;
|
||||
unget_bon8(byte);
|
||||
return get_bon8_string(s) && sax->string(s);
|
||||
}
|
||||
|
||||
// array with 0..4 elements
|
||||
if (byte <= 0x84)
|
||||
{
|
||||
return enter_array(static_cast<std::size_t>(byte - 0x80));
|
||||
}
|
||||
|
||||
// array terminated by 0xFE
|
||||
if (byte == 0x85)
|
||||
{
|
||||
return enter_array(npos);
|
||||
}
|
||||
|
||||
// object with 0..4 members
|
||||
if (byte <= 0x8A)
|
||||
{
|
||||
return enter_object(static_cast<std::size_t>(byte - 0x86));
|
||||
}
|
||||
|
||||
switch (byte)
|
||||
{
|
||||
case 0x8B: // object terminated by 0xFE
|
||||
return enter_object(npos);
|
||||
|
||||
case 0x8C: // int32
|
||||
{
|
||||
std::int32_t number{};
|
||||
return get_number(input_format_t::bon8, number) && emit_bon8_integer(number);
|
||||
}
|
||||
|
||||
case 0x8D: // int64
|
||||
{
|
||||
std::int64_t number{};
|
||||
return get_number(input_format_t::bon8, number) && emit_bon8_integer(number);
|
||||
}
|
||||
|
||||
case 0x8E: // binary32
|
||||
{
|
||||
float number{};
|
||||
return get_number(input_format_t::bon8, number) && sax->number_float(static_cast<number_float_t>(number), "");
|
||||
}
|
||||
|
||||
case 0x8F: // binary64
|
||||
{
|
||||
double number{};
|
||||
return get_number(input_format_t::bon8, number) && sax->number_float(static_cast<number_float_t>(number), "");
|
||||
}
|
||||
|
||||
case 0xF8:
|
||||
return sax->boolean(false);
|
||||
|
||||
case 0xF9:
|
||||
return sax->boolean(true);
|
||||
|
||||
case 0xFA:
|
||||
return sax->null();
|
||||
|
||||
case 0xFB:
|
||||
return sax->number_float(static_cast<number_float_t>(-1.0), "");
|
||||
|
||||
case 0xFC:
|
||||
return sax->number_float(static_cast<number_float_t>(0.0), "");
|
||||
|
||||
case 0xFD:
|
||||
return sax->number_float(static_cast<number_float_t>(1.0), "");
|
||||
|
||||
case 0xFF: // empty string
|
||||
{
|
||||
string_t s;
|
||||
return sax->string(s);
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// integer 0..39
|
||||
if (byte <= 0xB7)
|
||||
{
|
||||
return sax->number_unsigned(static_cast<number_unsigned_t>(byte - 0x90));
|
||||
}
|
||||
|
||||
// integer -1..-10
|
||||
if (byte <= 0xC1)
|
||||
{
|
||||
return sax->number_integer(-1 - static_cast<number_integer_t>(byte - 0xB8));
|
||||
}
|
||||
|
||||
// 0xC2..0xF7: a UTF-8 lead byte begins a string if a continuation
|
||||
// byte follows and an integer otherwise
|
||||
if (byte <= 0xF7)
|
||||
{
|
||||
const auto second = get_bon8();
|
||||
if (is_bon8_continuation(second))
|
||||
{
|
||||
string_t s;
|
||||
unget_bon8(second);
|
||||
unget_bon8(byte);
|
||||
return get_bon8_string(s) && sax->string(s);
|
||||
}
|
||||
return get_bon8_integer(byte, second);
|
||||
}
|
||||
|
||||
// 0xFE: end of container where a value is expected
|
||||
return bon8_error("invalid byte", "value");
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief pass an integer to the SAX parser
|
||||
|
||||
Non-negative integers are passed as unsigned, negative integers as signed
|
||||
numbers, like the other binary formats do.
|
||||
|
||||
@param[in] number the integer
|
||||
@return whether the SAX parser accepted the value
|
||||
*/
|
||||
bool emit_bon8_integer(const std::int64_t number)
|
||||
{
|
||||
if (number >= 0)
|
||||
{
|
||||
return sax->number_unsigned(static_cast<number_unsigned_t>(number));
|
||||
}
|
||||
return sax->number_integer(static_cast<number_integer_t>(number));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read an integer encoded in 2..4 bytes
|
||||
|
||||
The first byte is a UTF-8 lead byte (0xC2..0xF7) that is followed by a
|
||||
byte that is not a continuation byte: 0x00..0x7F for positive and
|
||||
0xC0..0xFF for negative integers. The lead byte's low bits and the second
|
||||
byte's low 7 (positive) or 6 (negative) bits are the most significant bits
|
||||
of the value; 3- and 4-byte integers add one or two full bytes. Each range
|
||||
starts where the shorter one ends, so no value has two encodings of the
|
||||
same length.
|
||||
|
||||
@param[in] lead the first byte (0xC2..0xF7)
|
||||
@param[in] second the second byte
|
||||
@return whether reading the integer succeeded
|
||||
*/
|
||||
bool get_bon8_integer(const char_int_type lead, const char_int_type second)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bon8, "number")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool negative = second >= 0xC0;
|
||||
auto value = static_cast<std::int64_t>(negative ? (second & 0x3F) : second);
|
||||
std::int64_t offset = 0;
|
||||
int extra_bytes = 0;
|
||||
|
||||
if (lead <= 0xDF)
|
||||
{
|
||||
value |= static_cast<std::int64_t>(lead - 0xC2) << (negative ? 6 : 7);
|
||||
offset = negative ? 11 : 40;
|
||||
}
|
||||
else if (lead <= 0xEF)
|
||||
{
|
||||
value |= static_cast<std::int64_t>(lead & 0x0F) << (negative ? 6 : 7);
|
||||
offset = negative ? 1931 : 3880;
|
||||
extra_bytes = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
value |= static_cast<std::int64_t>(lead & 0x07) << (negative ? 6 : 7);
|
||||
offset = negative ? 264075 : 528168;
|
||||
extra_bytes = 2;
|
||||
}
|
||||
|
||||
for (int i = 0; i < extra_bytes; ++i)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(get_bon8() == char_traits<char_type>::eof()))
|
||||
{
|
||||
return unexpect_eof(input_format_t::bon8, "number");
|
||||
}
|
||||
value = (value << 8) | static_cast<std::int64_t>(current);
|
||||
}
|
||||
|
||||
return negative ? sax->number_integer(static_cast<number_integer_t>(-(value + offset)))
|
||||
: sax->number_unsigned(static_cast<number_unsigned_t>(value + offset));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read an object key
|
||||
|
||||
A key must be a string, so its first byte must be an ASCII character, a
|
||||
UTF-8 lead byte followed by a continuation byte, or 0xFF (empty string).
|
||||
|
||||
@param[out] result the key
|
||||
@return whether reading the key succeeded
|
||||
*/
|
||||
bool get_bon8_key(string_t& result)
|
||||
{
|
||||
const auto byte = get_bon8();
|
||||
|
||||
if (byte == char_traits<char_type>::eof())
|
||||
{
|
||||
return unexpect_eof(input_format_t::bon8, "key");
|
||||
}
|
||||
|
||||
if (byte == 0xFF)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (byte <= 0x7F)
|
||||
{
|
||||
unget_bon8(byte);
|
||||
return get_bon8_string(result);
|
||||
}
|
||||
|
||||
if (0xC2 <= byte && byte <= 0xF7)
|
||||
{
|
||||
const auto second = get_bon8();
|
||||
unget_bon8(second);
|
||||
if (is_bon8_continuation(second))
|
||||
{
|
||||
unget_bon8(byte);
|
||||
return get_bon8_string(result);
|
||||
}
|
||||
// an integer: report its first byte rather than the one after it
|
||||
current = byte;
|
||||
}
|
||||
|
||||
return bon8_error("expected a string; last byte", "key");
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief append the run of valid UTF-8 at the read position to a string
|
||||
|
||||
For contiguous input, the ASCII characters and complete well-formed UTF-8
|
||||
sequences at the read position are appended to @a result in one step. The
|
||||
byte that stops the run (an end-of-string marker, the first byte of the
|
||||
next value, or an ill-formed byte) is left for @ref get_bon8_string, so
|
||||
that strings end and errors are reported exactly as without this step.
|
||||
|
||||
@param[in,out] result the string to append to
|
||||
*/
|
||||
void get_bon8_string_bulk(string_t& result, std::true_type /*bulk*/)
|
||||
{
|
||||
// bytes handed back must be read through get_bon8() first
|
||||
if (bon8_pushback_size != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const std::size_t remaining = ia.bulk_remaining();
|
||||
if (remaining == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||
const std::size_t length = valid_utf8_prefix(data, remaining);
|
||||
if (length != 0)
|
||||
{
|
||||
result.append(reinterpret_cast<const typename string_t::value_type*>(data), length);
|
||||
ia.bulk_skip(length);
|
||||
chars_read += length;
|
||||
}
|
||||
}
|
||||
|
||||
/// input that is not contiguous: strings are read byte by byte
|
||||
void get_bon8_string_bulk(string_t& /*result*/, std::false_type /*bulk*/) const noexcept {}
|
||||
|
||||
/*!
|
||||
@brief read a string
|
||||
|
||||
Reads UTF-8 characters until an end-of-string marker (0xFF), which is
|
||||
consumed, or a byte that cannot continue the string, which is handed back
|
||||
to be read as the start of the next value. The string must be valid UTF-8,
|
||||
and it must not end at the end of the input: the last string of a message
|
||||
is always terminated by 0xFF.
|
||||
|
||||
@param[out] result the string
|
||||
@return whether reading the string succeeded
|
||||
*/
|
||||
bool get_bon8_string(string_t& result)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
get_bon8_string_bulk(result, std::integral_constant<bool, bulk_scan> {});
|
||||
|
||||
const auto byte = get_bon8();
|
||||
|
||||
if (byte == char_traits<char_type>::eof())
|
||||
{
|
||||
return unexpect_eof(input_format_t::bon8, "string");
|
||||
}
|
||||
|
||||
// end of string
|
||||
if (byte == 0xFF)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// ASCII character
|
||||
if (byte <= 0x7F)
|
||||
{
|
||||
result.push_back(static_cast<typename string_t::value_type>(byte));
|
||||
continue;
|
||||
}
|
||||
|
||||
// a byte that cannot begin a character ends the string and begins
|
||||
// the next value
|
||||
if (byte < 0xC2 || byte > 0xF7)
|
||||
{
|
||||
unget_bon8(byte);
|
||||
return true;
|
||||
}
|
||||
|
||||
// a lead byte ends the string if no continuation byte follows: it
|
||||
// is then the first byte of an integer
|
||||
const auto second = get_bon8();
|
||||
if (!is_bon8_continuation(second))
|
||||
{
|
||||
unget_bon8(second);
|
||||
unget_bon8(byte);
|
||||
return true;
|
||||
}
|
||||
|
||||
// the valid range of the second byte excludes overlong forms,
|
||||
// surrogates, and code points above U+10FFFF
|
||||
// (RFC 3629, section 4)
|
||||
int continuation_bytes = 0;
|
||||
bool valid_second = true;
|
||||
if (byte <= 0xDF)
|
||||
{
|
||||
continuation_bytes = 1;
|
||||
}
|
||||
else if (byte <= 0xEF)
|
||||
{
|
||||
continuation_bytes = 2;
|
||||
valid_second = (byte != 0xE0 || second >= 0xA0) && (byte != 0xED || second <= 0x9F);
|
||||
}
|
||||
else
|
||||
{
|
||||
continuation_bytes = 3;
|
||||
valid_second = byte <= 0xF4 && (byte != 0xF0 || second >= 0x90) && (byte != 0xF4 || second <= 0x8F);
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!valid_second))
|
||||
{
|
||||
return bon8_error("invalid UTF-8 byte", "string");
|
||||
}
|
||||
|
||||
result.push_back(static_cast<typename string_t::value_type>(byte));
|
||||
result.push_back(static_cast<typename string_t::value_type>(second));
|
||||
|
||||
for (int i = 1; i < continuation_bytes; ++i)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(get_bon8() == char_traits<char_type>::eof()))
|
||||
{
|
||||
return unexpect_eof(input_format_t::bon8, "string");
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_bon8_continuation(current)))
|
||||
{
|
||||
return bon8_error("invalid UTF-8 byte", "string");
|
||||
}
|
||||
result.push_back(static_cast<typename string_t::value_type>(current));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
// Utility functions //
|
||||
///////////////////////
|
||||
@@ -3301,7 +3936,28 @@ class binary_reader
|
||||
const NumberType len,
|
||||
string_t& result)
|
||||
{
|
||||
return get_bytes(format, len, "string", result);
|
||||
// get_bytes() appends to result, and CBOR indefinite-length strings
|
||||
// collect all their chunks in the same result; validating only the
|
||||
// newly read bytes keeps the check linear in the input size
|
||||
const std::size_t old_size = result.size();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_bytes(format, len, "string", result)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// RFC 8949 (CBOR) §3.1 and the MessagePack/BSON/UBJSON specifications
|
||||
// all require text strings to be valid UTF-8; reject anything else
|
||||
// right here so malformed input is caught at decode time instead of
|
||||
// only surfacing later as a type_error.316 when the value is dumped
|
||||
// (which would defeat allow_exceptions=false / strict discarding).
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_valid_utf8(result, old_size)))
|
||||
{
|
||||
return sax->parse_error(chars_read, get_token_string(),
|
||||
parse_error::create(113, chars_read,
|
||||
exception_message(format, "invalid string: ill-formed UTF-8 byte", "string"), nullptr));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -3445,6 +4101,10 @@ class binary_reader
|
||||
error_msg += "BJData";
|
||||
break;
|
||||
|
||||
case input_format_t::bon8:
|
||||
error_msg += "BON8";
|
||||
break;
|
||||
|
||||
case input_format_t::json: // LCOV_EXCL_LINE
|
||||
default: // LCOV_EXCL_LINE
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
@@ -3477,6 +4137,11 @@ class binary_reader
|
||||
/// the containers that have been opened and not closed yet; see @ref container_frame
|
||||
std::vector<container_frame> container_stack{};
|
||||
|
||||
/// BON8: bytes read past the end of a string, returned again by @ref get_bon8
|
||||
std::array<char_int_type, 2> bon8_pushback{{}};
|
||||
/// BON8: number of bytes in @ref bon8_pushback
|
||||
std::size_t bon8_pushback_size = 0;
|
||||
|
||||
// excluded markers in bjdata optimized type
|
||||
#define JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_ \
|
||||
make_array<char_int_type>('F', 'H', 'N', 'S', 'T', 'Z', '[', '{')
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace detail
|
||||
{
|
||||
|
||||
/// the supported input formats
|
||||
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata };
|
||||
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
|
||||
|
||||
////////////////////
|
||||
// input adapters //
|
||||
@@ -101,6 +101,11 @@ class input_stream_adapter
|
||||
// maintain ifstream flags, except eof
|
||||
if (is != nullptr)
|
||||
{
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
// consume the character last returned by get_character() unless it
|
||||
// was given back with release_lookahead()
|
||||
commit_lookahead();
|
||||
#endif
|
||||
is->clear(is->rdstate() & std::ios::eofbit);
|
||||
}
|
||||
}
|
||||
@@ -114,6 +119,58 @@ class input_stream_adapter
|
||||
input_stream_adapter& operator=(input_stream_adapter&) = delete;
|
||||
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
||||
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||
: is(rhs.is), sb(rhs.sb), lookahead(rhs.lookahead)
|
||||
{
|
||||
rhs.is = nullptr;
|
||||
rhs.sb = nullptr;
|
||||
rhs.lookahead = false;
|
||||
}
|
||||
|
||||
// Whether the character last returned by get_character() can be given back
|
||||
// to the input with release_lookahead().
|
||||
static constexpr bool supports_lookahead = true;
|
||||
|
||||
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
|
||||
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
||||
// end up as the same value, e.g., 0xFFFFFFFF.
|
||||
//
|
||||
// The character is peeked rather than consumed: it is only stepped over
|
||||
// once the next character is requested, or when the adapter is destroyed.
|
||||
// Until then, release_lookahead() can leave it in the input.
|
||||
std::char_traits<char>::int_type get_character()
|
||||
{
|
||||
if (lookahead)
|
||||
{
|
||||
// step over the character returned by the previous call
|
||||
sb->sbumpc();
|
||||
}
|
||||
|
||||
auto res = sb->sgetc();
|
||||
// set eof manually, as we don't use the istream interface.
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||
{
|
||||
// there is nothing to step over next time
|
||||
lookahead = false;
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
else
|
||||
{
|
||||
lookahead = true;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Leave the character last returned by get_character() in the input, so
|
||||
// that the next read from the stream - by this adapter or by the caller
|
||||
// once parsing is done - sees it again. Unlike putting a consumed
|
||||
// character back, this cannot fail.
|
||||
void release_lookahead() noexcept
|
||||
{
|
||||
lookahead = false;
|
||||
}
|
||||
#else
|
||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||
: is(rhs.is), sb(rhs.sb)
|
||||
{
|
||||
@@ -124,6 +181,9 @@ class input_stream_adapter
|
||||
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
|
||||
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
||||
// end up as the same value, e.g., 0xFFFFFFFF.
|
||||
//
|
||||
// The character is consumed, so the character that terminates a number
|
||||
// stays consumed after parsing; see JSON_PRECISE_STREAM_POSITION.
|
||||
std::char_traits<char>::int_type get_character()
|
||||
{
|
||||
auto res = sb->sbumpc();
|
||||
@@ -134,10 +194,14 @@ class input_stream_adapter
|
||||
}
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
template<class T>
|
||||
std::size_t get_elements(T* dest, std::size_t count = 1)
|
||||
{
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
commit_lookahead();
|
||||
#endif
|
||||
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
||||
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
||||
{
|
||||
@@ -147,9 +211,27 @@ class input_stream_adapter
|
||||
}
|
||||
|
||||
private:
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
// Step over the character last returned by get_character(). The character
|
||||
// has already been peeked successfully, so for every streambuf with a get
|
||||
// area this is a pointer increment that cannot fail.
|
||||
void commit_lookahead()
|
||||
{
|
||||
if (lookahead)
|
||||
{
|
||||
lookahead = false;
|
||||
sb->sbumpc();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// the associated input stream
|
||||
std::istream* is = nullptr;
|
||||
std::streambuf* sb = nullptr;
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
/// whether get_character() peeked a character that is not consumed yet
|
||||
bool lookahead = false;
|
||||
#endif
|
||||
};
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
@@ -762,6 +844,21 @@ contiguous_bytes_input_adapter input_adapter(CharT b)
|
||||
template<typename T, std::size_t N>
|
||||
auto input_adapter(T (&array)[N]) -> decltype(input_adapter(array, array + N)) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
#if JSON_STRICT_NUL_HANDLING
|
||||
// A `char` array from string-literal initialization (e.g. json::parse("123"))
|
||||
// carries a trailing '\0' contributed by the compiler, not by the source
|
||||
// text; drop exactly that one byte so it is not mistaken for real trailing
|
||||
// data. Every other element type (unsigned char, std::uint8_t, ...) keeps
|
||||
// the full extent unconditionally, since a trailing zero byte there is
|
||||
// genuine data (e.g. CBOR/MessagePack). This intentionally does not
|
||||
// strlen()-scan the array (as the pointer overload above does for a
|
||||
// null-delimited string): for a `char` array that is not NUL-terminated
|
||||
// within its bounds, that would read past the end of the array.
|
||||
if (std::is_same<typename std::remove_cv<T>::type, char>::value && N > 0 && array[N - 1] == 0)
|
||||
{
|
||||
return input_adapter(array, array + N - 1);
|
||||
}
|
||||
#endif
|
||||
return input_adapter(array, array + N);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // min
|
||||
#include <algorithm> // find_if, min
|
||||
#include <cstddef>
|
||||
#include <string> // string
|
||||
#include <type_traits> // enable_if_t
|
||||
#include <utility> // move
|
||||
#include <utility> // move, pair
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/detail/exceptions.hpp>
|
||||
@@ -302,7 +302,7 @@ class json_sax_dom_parser
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
||||
return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -351,7 +351,7 @@ class json_sax_dom_parser
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||
return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||
}
|
||||
|
||||
if (len != detail::unknown_size())
|
||||
@@ -635,7 +635,7 @@ class json_sax_dom_callback_parser
|
||||
// check object limit
|
||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
||||
return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -655,7 +655,17 @@ class json_sax_dom_callback_parser
|
||||
// add discarded value at the given key and store the reference for later
|
||||
if (keep && ref_stack.back())
|
||||
{
|
||||
object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded);
|
||||
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
||||
const auto it = obj.find(val);
|
||||
if (it != obj.end())
|
||||
{
|
||||
// this is a duplicate key (legal in JSON); remember its
|
||||
// current value so it can be restored later if the new
|
||||
// value is rejected by the callback, instead of being
|
||||
// erased together with the discarded placeholder
|
||||
duplicate_key_stash.emplace_back(&(it->second), it->second);
|
||||
}
|
||||
object_element = &(obj[val] = discarded);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -667,13 +677,18 @@ class json_sax_dom_callback_parser
|
||||
{
|
||||
if (!callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back()))
|
||||
{
|
||||
// discard object
|
||||
*ref_stack.back() = discarded;
|
||||
// discard object, unless this slot holds a duplicate key's
|
||||
// previous value pending restoration, in which case that
|
||||
// value is restored instead of being discarded
|
||||
if (!resolve_duplicate_key_stash(ref_stack.back(), true))
|
||||
{
|
||||
*ref_stack.back() = discarded;
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// Set start/end positions for discarded object.
|
||||
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
||||
// Set start/end positions for discarded object.
|
||||
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -687,6 +702,10 @@ class json_sax_dom_callback_parser
|
||||
#endif
|
||||
|
||||
ref_stack.back()->set_parents();
|
||||
// this object is finally, definitively kept; drop any
|
||||
// pending duplicate-key stash entry for its slot since it
|
||||
// can no longer be restored
|
||||
resolve_duplicate_key_stash(ref_stack.back(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -735,7 +754,7 @@ class json_sax_dom_callback_parser
|
||||
// check array limit
|
||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||
return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||
}
|
||||
|
||||
if (len != detail::unknown_size())
|
||||
@@ -767,16 +786,25 @@ class json_sax_dom_callback_parser
|
||||
#endif
|
||||
|
||||
ref_stack.back()->set_parents();
|
||||
// this array is finally, definitively kept; drop any
|
||||
// pending duplicate-key stash entry for its slot since it
|
||||
// can no longer be restored
|
||||
resolve_duplicate_key_stash(ref_stack.back(), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// discard array
|
||||
*ref_stack.back() = discarded;
|
||||
// discard array, unless this slot holds a duplicate key's
|
||||
// previous value pending restoration, in which case that
|
||||
// value is restored instead of being discarded
|
||||
if (!resolve_duplicate_key_stash(ref_stack.back(), true))
|
||||
{
|
||||
*ref_stack.back() = discarded;
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// Set start/end positions for discarded array.
|
||||
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
||||
// Set start/end positions for discarded array.
|
||||
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -893,6 +921,35 @@ class json_sax_dom_callback_parser
|
||||
}
|
||||
#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
|
||||
/// new value at that slot was rejected); otherwise the stash entry is
|
||||
/// simply dropped (use this when the new value was accepted, so it
|
||||
/// correctly supersedes the old one and no restore should ever happen
|
||||
/// for this slot again)
|
||||
/// @return whether a matching stash entry was found (and processed)
|
||||
bool resolve_duplicate_key_stash(BasicJsonType* slot, bool restore_value)
|
||||
{
|
||||
const auto it = std::find_if(duplicate_key_stash.begin(), duplicate_key_stash.end(),
|
||||
[slot](const std::pair<BasicJsonType*, BasicJsonType>& entry)
|
||||
{
|
||||
return entry.first == slot;
|
||||
});
|
||||
|
||||
if (it == duplicate_key_stash.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (restore_value)
|
||||
{
|
||||
*slot = std::move(it->second);
|
||||
}
|
||||
duplicate_key_stash.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the key the value now being handled will be stored under
|
||||
|
||||
@@ -911,7 +968,9 @@ class json_sax_dom_callback_parser
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief remove the discarded value the callback rejected from its parent
|
||||
@brief remove the discarded value the callback rejected from its parent,
|
||||
unless it is a duplicate key's slot with a stashed previous value, in
|
||||
which case that previous value is restored instead
|
||||
|
||||
A rejected value can only ever be the one most recently added to @a parent:
|
||||
the last element of an array, or the placeholder key() stored under @a key
|
||||
@@ -926,7 +985,7 @@ class json_sax_dom_callback_parser
|
||||
@param[in,out] parent the container to remove the rejected value from
|
||||
@param[in] key the key the value was stored under; unused for arrays
|
||||
*/
|
||||
static void remove_discarded_value(BasicJsonType& parent, const string_t& key)
|
||||
void remove_discarded_value(BasicJsonType& parent, const string_t& key)
|
||||
{
|
||||
if (parent.is_array())
|
||||
{
|
||||
@@ -942,7 +1001,12 @@ class json_sax_dom_callback_parser
|
||||
const auto it = object.find(key);
|
||||
if (it != object.end() && it->second.is_discarded())
|
||||
{
|
||||
object.erase(it);
|
||||
// a duplicate key's slot has a stashed previous value that
|
||||
// must be restored instead of being erased
|
||||
if (!resolve_duplicate_key_stash(&it->second, true))
|
||||
{
|
||||
object.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1044,6 +1108,16 @@ class json_sax_dom_callback_parser
|
||||
|
||||
JSON_ASSERT(object_element);
|
||||
*object_element = std::move(value);
|
||||
if (!skip_callback)
|
||||
{
|
||||
// this scalar value finally, definitively replaces whatever was
|
||||
// at this slot; drop any pending duplicate-key stash entry for
|
||||
// it since it can no longer be restored (a container value at
|
||||
// this slot is resolved later, in end_object()/end_array(),
|
||||
// since skip_callback is true for the placeholder handling that
|
||||
// happens here for those)
|
||||
resolve_duplicate_key_stash(object_element, false);
|
||||
}
|
||||
return {true, object_element};
|
||||
}
|
||||
|
||||
@@ -1063,6 +1137,12 @@ class json_sax_dom_callback_parser
|
||||
std::vector<string_t> container_key_stack {}; // NOLINT(readability-redundant-member-init)
|
||||
/// helper to hold the reference for the next object element
|
||||
BasicJsonType* object_element = nullptr;
|
||||
/// stash of (slot pointer, previous value) for object members that
|
||||
/// already existed when key() was called again for the same key
|
||||
/// (duplicate keys); used to restore the previous value if the new
|
||||
/// value is later rejected by the callback, instead of erasing the
|
||||
/// member entirely
|
||||
std::vector<std::pair<BasicJsonType*, BasicJsonType>> duplicate_key_stash {};
|
||||
/// whether a syntax error occurred
|
||||
bool errored = false;
|
||||
/// callback function
|
||||
|
||||
@@ -127,6 +127,25 @@ constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect whether an input adapter reads with one character of lookahead that
|
||||
// can be left in the input (see input_stream_adapter::supports_lookahead,
|
||||
// which is only defined with JSON_PRECISE_STREAM_POSITION), detected like
|
||||
// supports_seek above.
|
||||
template<typename InputAdapterType>
|
||||
using detect_supports_lookahead = decltype(InputAdapterType::supports_lookahead);
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_supports_lookahead(std::true_type /*detected*/)
|
||||
{
|
||||
return InputAdapterType::supports_lookahead;
|
||||
}
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_supports_lookahead(std::false_type /*detected*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect whether an input adapter exposes a contiguous byte block that the
|
||||
// lexer can scan directly (see iterator_input_adapter::supports_bulk_scan).
|
||||
// Adapters without the flag - file, stream, wide-string, user-defined - fall
|
||||
@@ -167,6 +186,12 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
static constexpr bool lazy_token_string =
|
||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||
|
||||
/// whether a simulated unget can be passed on to the input adapter, which
|
||||
/// then leaves the character in the input; see
|
||||
/// input_adapter_supports_lookahead
|
||||
static constexpr bool can_release_lookahead =
|
||||
input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {});
|
||||
|
||||
/// whether string scanning may bulk-consume runs of ordinary characters
|
||||
/// directly from a contiguous input buffer (SWAR fast path). This requires
|
||||
/// the token to be reconstructible lazily (lazy_token_string), so bypassing
|
||||
@@ -952,7 +977,9 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
case '\n':
|
||||
case '\r':
|
||||
case char_traits<char_type>::eof():
|
||||
#if !JSON_STRICT_NUL_HANDLING
|
||||
case '\0':
|
||||
#endif
|
||||
return true;
|
||||
|
||||
default:
|
||||
@@ -970,8 +997,10 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
{
|
||||
switch (get())
|
||||
{
|
||||
case char_traits<char_type>::eof():
|
||||
#if !JSON_STRICT_NUL_HANDLING
|
||||
case '\0':
|
||||
#endif
|
||||
case char_traits<char_type>::eof():
|
||||
{
|
||||
error_message = "invalid comment; missing closing '*/'";
|
||||
return false;
|
||||
@@ -1894,6 +1923,21 @@ scan_number_done:
|
||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||
}
|
||||
|
||||
/// adapter without lookahead: nothing to do (see release_lookahead)
|
||||
void release_lookahead_impl(std::false_type /*can_release*/) const noexcept {}
|
||||
|
||||
/// adapter with lookahead: leave the character in the input instead
|
||||
void release_lookahead_impl(std::true_type /*can_release*/)
|
||||
{
|
||||
if (next_unget)
|
||||
{
|
||||
// the character is read from the input again rather than replayed
|
||||
// from current, so the adapter must not step over it
|
||||
next_unget = false;
|
||||
ia.release_lookahead();
|
||||
}
|
||||
}
|
||||
|
||||
/// seekable adapter: nothing was captured, so nothing to undo
|
||||
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
@@ -1957,6 +2001,31 @@ scan_number_done:
|
||||
return position;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief pass a pending simulated unget on to the input
|
||||
|
||||
unget() only rewinds the lexer's own bookkeeping, so the character that
|
||||
terminated the last token (e.g. the character after a number) would still
|
||||
be stepped over when the input adapter is done. Callers that hand the
|
||||
input back to the user afterwards - operator>> and non-strict sax_parse -
|
||||
call this once when scanning is done, so that the input is positioned
|
||||
right after the value.
|
||||
|
||||
Adapters without lookahead (see input_adapter_supports_lookahead) are not
|
||||
handed back to the user, so this is a no-op for them. Without
|
||||
JSON_PRECISE_STREAM_POSITION, no adapter has lookahead, so this is always a
|
||||
no-op and the terminating character stays consumed.
|
||||
|
||||
Scanning may continue after this call: @a next_unget is cleared, and the
|
||||
character is read from the input again instead of being replayed from
|
||||
@a current. A pending unget of EOF needs no special case, because reaching
|
||||
EOF leaves no lookahead to release.
|
||||
*/
|
||||
void release_lookahead()
|
||||
{
|
||||
release_lookahead_impl(std::integral_constant<bool, can_release_lookahead> {});
|
||||
}
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
/// return the offset of the first character of the last read token; unlike
|
||||
/// the token's parsed value, this accounts for escape sequences
|
||||
@@ -2153,9 +2222,12 @@ scan_number_done:
|
||||
case '9':
|
||||
return scan_number_dispatch(std::integral_constant<bool, bulk_scan> {});
|
||||
|
||||
// end of input (the null byte is needed when parsing from
|
||||
// string literals)
|
||||
#if !JSON_STRICT_NUL_HANDLING
|
||||
case '\0':
|
||||
#endif
|
||||
// end of input; by default, a null byte is also treated as end of
|
||||
// input for backwards compatibility (see JSON_STRICT_NUL_HANDLING
|
||||
// to opt into rejecting a null byte in the input instead)
|
||||
case char_traits<char_type>::eof():
|
||||
return token_type::end_of_input;
|
||||
|
||||
|
||||
@@ -100,13 +100,22 @@ class parser
|
||||
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
||||
sax_parse_internal(&sdp);
|
||||
|
||||
// in strict mode, input must be completely read
|
||||
if (strict && (get_token() != token_type::end_of_input))
|
||||
if (strict)
|
||||
{
|
||||
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));
|
||||
// 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
|
||||
@@ -128,12 +137,20 @@ class parser
|
||||
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
||||
sax_parse_internal(&sdp);
|
||||
|
||||
// in strict mode, input must be completely read
|
||||
if (strict && (get_token() != token_type::end_of_input))
|
||||
if (strict)
|
||||
{
|
||||
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));
|
||||
// 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
|
||||
@@ -166,12 +183,24 @@ class parser
|
||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||
const bool result = sax_parse_internal(sax);
|
||||
|
||||
// strict mode: next byte must be EOF
|
||||
if (result && strict && (get_token() != token_type::end_of_input))
|
||||
if (result)
|
||||
{
|
||||
return sax->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));
|
||||
if (strict)
|
||||
{
|
||||
// strict mode: next byte must be EOF
|
||||
if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
return sax->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 result;
|
||||
|
||||
@@ -201,6 +201,43 @@ inline std::size_t validate_one_utf8(const unsigned char* data, std::size_t avai
|
||||
return 0; // invalid, incomplete, or must be diagnosed by the byte path
|
||||
}
|
||||
|
||||
// Return the length of the longest prefix of [data, data+n) that consists of
|
||||
// ASCII characters and complete well-formed UTF-8 sequences; n if all of it is
|
||||
// valid UTF-8. Unlike scalar_string_bulk_run(), quotes, escapes, and control
|
||||
// characters are ordinary characters here. ASCII is skipped 8 bytes at a time.
|
||||
inline std::size_t valid_utf8_prefix(const unsigned char* data, std::size_t n) noexcept
|
||||
{
|
||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||
std::size_t pos = 0;
|
||||
while (pos < n)
|
||||
{
|
||||
if (pos + 8 <= n)
|
||||
{
|
||||
std::uint64_t word = 0;
|
||||
std::memcpy(&word, data + pos, sizeof(word));
|
||||
if ((word & high) == 0)
|
||||
{
|
||||
pos += 8;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (data[pos] < 0x80u)
|
||||
{
|
||||
++pos;
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::size_t seq = validate_one_utf8(data + pos, n - pos);
|
||||
if (seq == 0)
|
||||
{
|
||||
break; // ill-formed or truncated
|
||||
}
|
||||
pos += seq;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
// Scalar (C++11) computation of the bulk run length: the number of leading
|
||||
// bytes in [data, data+n) that are ordinary ASCII or complete well-formed UTF-8
|
||||
// sequences, stopping before the first byte that needs individual handling (the
|
||||
|
||||
@@ -88,8 +88,13 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
|
||||
iter_impl() = default;
|
||||
~iter_impl() = default;
|
||||
iter_impl(iter_impl&&) noexcept = default;
|
||||
iter_impl& operator=(iter_impl&&) noexcept = default;
|
||||
// the exception specification is left to be computed rather than declared:
|
||||
// an array or object type whose iterator is not nothrow move constructible
|
||||
// (std::deque's is not before libstdc++ 11) would make a declared noexcept
|
||||
// differ from the implicit one, which deletes the function -- and is an
|
||||
// error outright with older compilers
|
||||
iter_impl(iter_impl&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations)
|
||||
iter_impl& operator=(iter_impl&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations)
|
||||
|
||||
/*!
|
||||
@brief constructor for a given JSON instance
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#endif // JSON_NO_IO
|
||||
#include <limits> // max
|
||||
#include <numeric> // accumulate
|
||||
#include <set> // set
|
||||
#include <string> // string
|
||||
#include <utility> // move
|
||||
#include <vector> // vector
|
||||
@@ -72,7 +73,7 @@ class json_pointer
|
||||
string_t{},
|
||||
[](const string_t& a, const string_t& b)
|
||||
{
|
||||
return detail::concat(a, '/', detail::escape(b));
|
||||
return detail::concat<string_t>(a, '/', detail::escape(b));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -266,7 +267,7 @@ class json_pointer
|
||||
JSON_THROW(detail::parse_error::create(109, 0, detail::concat("array index '", s, "' is not a number"), nullptr));
|
||||
}
|
||||
|
||||
const char* p = s.c_str();
|
||||
const char* p = s.data();
|
||||
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||
errno = 0; // strtoull doesn't reset errno
|
||||
const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)
|
||||
@@ -301,19 +302,35 @@ class json_pointer
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief the reference token sequences that denote arrays
|
||||
|
||||
@ref unflatten collects the pointer prefixes that have a reference token 0
|
||||
among their children; @ref get_and_create creates arrays exactly below
|
||||
those prefixes and objects everywhere else. Deciding this up front keeps
|
||||
the result independent of the order in which the flattened object is
|
||||
iterated, which is unspecified for some object types.
|
||||
*/
|
||||
using array_parents_t = std::set<std::vector<string_t>>;
|
||||
|
||||
/*!
|
||||
@brief create and return a reference to the pointed to value
|
||||
|
||||
@complexity Linear in the number of reference tokens.
|
||||
|
||||
@throw parse_error.106 if an array index begins with '0'
|
||||
@throw parse_error.109 if array index is not a number
|
||||
@throw type_error.313 if value cannot be unflattened
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
BasicJsonType& get_and_create(BasicJsonType& j) const
|
||||
BasicJsonType& get_and_create(BasicJsonType& j, const array_parents_t& array_parents) const
|
||||
{
|
||||
auto* result = &j;
|
||||
|
||||
// the reference tokens that have been consumed so far; used to look up
|
||||
// whether the value to be created below is an array or an object
|
||||
std::vector<string_t> prefix;
|
||||
|
||||
// in case no reference tokens exist, return a reference to the JSON value
|
||||
// j which will be overwritten by a primitive value
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
@@ -322,10 +339,11 @@ class json_pointer
|
||||
{
|
||||
case detail::value_t::null:
|
||||
{
|
||||
if (reference_token == "0")
|
||||
if (array_parents.find(prefix) != array_parents.end())
|
||||
{
|
||||
// start a new array if the reference token is 0
|
||||
result = &result->operator[](0);
|
||||
// some reference token below this position is 0, so the
|
||||
// value is an array
|
||||
result = &result->operator[](array_index<BasicJsonType>(reference_token));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -365,6 +383,8 @@ class json_pointer
|
||||
default:
|
||||
JSON_THROW(detail::type_error::create(313, "invalid value to unflatten", &j));
|
||||
}
|
||||
|
||||
prefix.push_back(reference_token);
|
||||
}
|
||||
|
||||
return *result;
|
||||
@@ -838,7 +858,8 @@ class json_pointer
|
||||
{
|
||||
// use the text between the beginning of the reference token
|
||||
// (start) and the last slash (slash).
|
||||
auto reference_token = reference_string.substr(start, slash - start);
|
||||
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
|
||||
auto reference_token = string_t(reference_string.data() + start, count);
|
||||
|
||||
// check reference tokens are properly escaped
|
||||
for (std::size_t pos = reference_token.find_first_of('~');
|
||||
@@ -954,6 +975,24 @@ class json_pointer
|
||||
|
||||
BasicJsonType result;
|
||||
|
||||
// collect the pointer prefixes that have a reference token 0 among
|
||||
// their children; the values below them are arrays, all others are
|
||||
// objects (see array_parents_t)
|
||||
array_parents_t array_parents;
|
||||
for (const auto& element : *value.m_data.m_value.object)
|
||||
{
|
||||
json_pointer ptr(element.first);
|
||||
std::vector<string_t> prefix;
|
||||
for (auto& reference_token : ptr.reference_tokens)
|
||||
{
|
||||
if (reference_token == "0")
|
||||
{
|
||||
array_parents.insert(prefix);
|
||||
}
|
||||
prefix.push_back(std::move(reference_token));
|
||||
}
|
||||
}
|
||||
|
||||
// iterate the JSON object values
|
||||
for (const auto& element : *value.m_data.m_value.object)
|
||||
{
|
||||
@@ -966,7 +1005,7 @@ class json_pointer
|
||||
// that if the JSON pointer is "" (i.e., points to the whole value),
|
||||
// function get_and_create returns a reference to the result itself.
|
||||
// An assignment will then create a primitive value.
|
||||
json_pointer(element.first).get_and_create(result) = element.second;
|
||||
json_pointer(element.first).get_and_create(result, array_parents) = element.second;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -186,6 +186,15 @@
|
||||
#define JSON_NO_UNIQUE_ADDRESS
|
||||
#endif
|
||||
|
||||
// Clang targeting MinGW does not survive the thread_local storage the copy
|
||||
// constructor uses to bound its descent: every test that copies a value
|
||||
// segfaults with clang 11.0.1 and clang 18.1.8, while the same tests pass with
|
||||
// GCC targeting MinGW and with every other toolchain the library is tested on.
|
||||
// Copying works the same way without the counter, only more slowly.
|
||||
#if !defined(JSON_NO_THREAD_LOCAL) && defined(__clang__) && defined(__MINGW32__)
|
||||
#define JSON_NO_THREAD_LOCAL 1
|
||||
#endif
|
||||
|
||||
// disable documentation warnings on clang
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
@@ -587,16 +596,62 @@ void templated_json_throw(ExceptionType exception)
|
||||
|
||||
#define NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(...) template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> __VA_ARGS__
|
||||
|
||||
// Helpers used to dispatch the NLOHMANN_DEFINE_TYPE_*/NLOHMANN_DEFINE_DERIVED_TYPE_*
|
||||
// macros below between a zero-member and a one-or-more-member implementation
|
||||
// (issue #4041, e.g. NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type) with no further
|
||||
// arguments). NLOHMANN_JSON_TYPE_BODY(Prefix, ...) expands to the macro name
|
||||
// Prefix##EMPTY when __VA_ARGS__ is a single argument (Type alone) and to
|
||||
// Prefix##MEMBERS for two or more (Type, member...). It reuses the existing
|
||||
// 64-slot NLOHMANN_JSON_GET_MACRO dispatch with one extra trailing sentinel
|
||||
// token appended so its own trailing "..." is never left completely empty at
|
||||
// the lowest supported argument count -- invoking a variadic macro so that
|
||||
// "..." matches nothing is only granted unconditionally by the standard since
|
||||
// C++20, and pre-C++20 compilers may reject it under -pedantic regardless of
|
||||
// what the macro body does.
|
||||
//
|
||||
// The EMPTY/MEMBERS suffixes are pasted onto Prefix right in the slot table:
|
||||
// operands of ## are not macro-expanded, so the dispatch keeps working even if
|
||||
// user code defines macros named EMPTY or MEMBERS. Producing the bare suffix
|
||||
// first and pasting it later would let such a macro replace it.
|
||||
//
|
||||
// NLOHMANN_JSON_DERIVED_TYPE_BODY(Prefix, ...) answers the same question for
|
||||
// the derived-type macros, whose fixed prefix is Type,BaseType. It drops the
|
||||
// leading Type and defers to NLOHMANN_JSON_TYPE_BODY rather than shifting the
|
||||
// slot table by one: NLOHMANN_JSON_GET_MACRO only resolves 64 positional
|
||||
// arguments, so dispatching on Type,BaseType,member... directly would run out
|
||||
// one slot early and cap the derived-type macros at 62 members instead of the
|
||||
// 63 that NLOHMANN_JSON_PASTE supports.
|
||||
#define NLOHMANN_JSON_TYPE_BODY(Prefix, ...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## EMPTY, \
|
||||
NLOHMANN_JSON_TYPE_BODY_SENTINEL))
|
||||
|
||||
#define NLOHMANN_JSON_DERIVED_TYPE_BODY_(Prefix, Type, ...) NLOHMANN_JSON_TYPE_BODY(Prefix, __VA_ARGS__)
|
||||
#define NLOHMANN_JSON_DERIVED_TYPE_BODY(Prefix, ...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY_(Prefix, __VA_ARGS__))
|
||||
|
||||
/*!
|
||||
@brief macro
|
||||
@def NLOHMANN_DEFINE_TYPE_INTRUSIVE
|
||||
@since version 3.9.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_MEMBERS(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_EMPTY(Type) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type&) { nlohmann_json_j = BasicJsonType::object(); }) \
|
||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) Type is used as a declarator type, not in an expression */ \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType&, Type&) noexcept { })
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_NAMES(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -607,10 +662,15 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.11.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) })
|
||||
|
||||
// identical to NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_EMPTY: with no members there is nothing to default
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_EMPTY(Type) NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_EMPTY(Type)
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -621,9 +681,14 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.11.3
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_MEMBERS(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_EMPTY(Type) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type&) { nlohmann_json_j = BasicJsonType::object(); })
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) })
|
||||
|
||||
@@ -633,10 +698,17 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.9.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_MEMBERS(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_EMPTY(Type) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type&) { nlohmann_json_j = BasicJsonType::object(); }) \
|
||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) Type is used as a declarator type, not in an expression */ \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType&, Type&) noexcept { })
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -647,10 +719,15 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.11.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) })
|
||||
|
||||
// identical to NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_EMPTY: with no members there is nothing to default
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_EMPTY(Type) NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_EMPTY(Type)
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -661,9 +738,14 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.11.3
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_MEMBERS(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_EMPTY(Type) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type&) { nlohmann_json_j = BasicJsonType::object(); })
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) })
|
||||
|
||||
@@ -673,10 +755,17 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.12.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_MEMBERS(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_EMPTY(Type, BaseType) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); }) \
|
||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) Type/BaseType are used as declarator types, not in expressions */ \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); })
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_NAMES(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -687,10 +776,15 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.12.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) })
|
||||
|
||||
// identical to NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_EMPTY: with no members there is nothing to default
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_EMPTY(Type, BaseType) NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_EMPTY(Type, BaseType)
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -701,9 +795,14 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.12.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_MEMBERS(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_EMPTY(Type, BaseType) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); })
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) })
|
||||
|
||||
@@ -714,10 +813,17 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.12.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_MEMBERS(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_EMPTY(Type, BaseType) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); }) \
|
||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) Type/BaseType are used as declarator types, not in expressions */ \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); })
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_NAMES(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -728,10 +834,15 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.12.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) })
|
||||
|
||||
// identical to NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_EMPTY: with no members there is nothing to default
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_EMPTY(Type, BaseType) NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_EMPTY(Type, BaseType)
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -742,9 +853,14 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.12.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_MEMBERS(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_EMPTY(Type, BaseType) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); })
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) })
|
||||
|
||||
@@ -803,7 +919,3 @@ void templated_json_throw(ExceptionType exception)
|
||||
#ifndef JSON_USE_GLOBAL_UDLS
|
||||
#define JSON_USE_GLOBAL_UDLS 1
|
||||
#endif
|
||||
|
||||
#ifndef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
|
||||
#endif
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#undef JSON_NO_UNIQUE_ADDRESS
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_USE_GLOBAL_UDLS
|
||||
#undef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
|
||||
#ifndef JSON_TEST_KEEP_MACROS
|
||||
#undef JSON_CATCH
|
||||
@@ -44,6 +43,9 @@
|
||||
#undef JSON_HAS_STD_FORMAT
|
||||
#undef JSON_HAS_STATIC_RTTI
|
||||
#undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
#undef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#undef JSON_PRECISE_STREAM_POSITION
|
||||
#undef JSON_STRICT_NUL_HANDLING
|
||||
#endif
|
||||
|
||||
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
||||
|
||||
@@ -172,17 +172,18 @@ struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json<T>::value >>
|
||||
template<typename T>
|
||||
using detect_key_compare = typename T::key_compare;
|
||||
|
||||
template<typename T>
|
||||
struct has_key_compare : std::integral_constant<bool, is_detected<detect_key_compare, T>::value> {};
|
||||
|
||||
// obtains the actual object key comparator
|
||||
// obtains the actual object key comparator: object_t::key_compare if the
|
||||
// object type defines it, and default_object_comparator_t otherwise
|
||||
//
|
||||
// note detected_or_t is used rather than std::conditional, because the latter
|
||||
// names both of its type arguments eagerly; object_t::key_compare would then
|
||||
// be a hard error for an object type that does not define it
|
||||
template<typename BasicJsonType>
|
||||
struct actual_object_comparator
|
||||
{
|
||||
using object_t = typename BasicJsonType::object_t;
|
||||
using object_comparator_t = typename BasicJsonType::default_object_comparator_t;
|
||||
using type = typename std::conditional < has_key_compare<object_t>::value,
|
||||
typename object_t::key_compare, object_comparator_t>::type;
|
||||
using type = detected_or_t<object_comparator_t, detect_key_compare, object_t>;
|
||||
};
|
||||
|
||||
template<typename BasicJsonType>
|
||||
@@ -778,6 +779,22 @@ using has_erase_with_key_type = typename std::conditional <
|
||||
std::true_type,
|
||||
std::false_type >::type;
|
||||
|
||||
template<typename ObjectType, typename IteratorType>
|
||||
using detect_erase_with_iterator = decltype(std::declval<ObjectType&>().erase(std::declval<IteratorType>()));
|
||||
|
||||
// type trait to check if erase(iterator) returns void instead of the following
|
||||
// iterator, as the object types that do not compute a successor the caller may
|
||||
// not need do
|
||||
template<typename ObjectType, typename IteratorType>
|
||||
using erase_returns_void = is_detected_exact<void, detect_erase_with_iterator, ObjectType, IteratorType>;
|
||||
|
||||
template<typename T>
|
||||
using detect_capacity = decltype(std::declval<const T&>().capacity());
|
||||
|
||||
// type trait to check if a type has a capacity() member function
|
||||
template<typename T>
|
||||
struct has_capacity : std::integral_constant<bool, is_detected<detect_capacity, T>::value> {};
|
||||
|
||||
// a naive helper to check if a type is an ordered_map (exploits the fact that
|
||||
// ordered_map inherits capacity() from std::vector)
|
||||
template <typename T>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@
|
||||
#include <iterator> // back_inserter
|
||||
#include <memory> // shared_ptr, make_shared
|
||||
#include <string> // basic_string
|
||||
#include <utility> // move
|
||||
#include <vector> // vector
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
@@ -44,22 +45,32 @@ template<typename CharType> struct output_adapter_protocol
|
||||
template<typename CharType>
|
||||
using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
|
||||
|
||||
/// output adapter for byte vectors
|
||||
/// @brief non-virtual output sink writing into a std::vector
|
||||
///
|
||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||
/// passed to binary_writer by value as a template parameter, so
|
||||
/// write_character()/write_characters() are ordinary (inlinable) calls with no
|
||||
/// vtable lookup and no shared_ptr. It is used for the common
|
||||
/// `to_cbor`/`to_msgpack`/... into a std::vector. output_vector_adapter below
|
||||
/// wraps this same sink to provide the virtual interface.
|
||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||
class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
class output_vector_sink
|
||||
{
|
||||
public:
|
||||
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: v(vec)
|
||||
{}
|
||||
|
||||
void write_character(CharType c) override
|
||||
void write_character(CharType c)
|
||||
{
|
||||
v.push_back(c);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
void write_characters(const CharType* s, std::size_t length) override
|
||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||
// pointer with length 0 for empty strings/binary values. Appending an empty
|
||||
// range is a no-op; the type-erased path tolerates this via the (unattributed)
|
||||
// virtual base, and the concrete sink must do the same.
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
v.insert(v.end(), s, s + length);
|
||||
}
|
||||
@@ -68,6 +79,34 @@ class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
std::vector<CharType, AllocatorType>& v;
|
||||
};
|
||||
|
||||
/// output adapter for byte vectors
|
||||
///
|
||||
/// The appending itself lives in output_vector_sink; this class only adds the
|
||||
/// virtual output_adapter_protocol interface on top of it, so both the
|
||||
/// type-erased and the templated path share one implementation.
|
||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||
class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
{
|
||||
public:
|
||||
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: sink(vec)
|
||||
{}
|
||||
|
||||
void write_character(CharType c) override
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
private:
|
||||
output_vector_sink<CharType, AllocatorType> sink;
|
||||
};
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
/// output adapter for output streams
|
||||
template<typename CharType>
|
||||
@@ -118,6 +157,39 @@ class output_string_adapter : public output_adapter_protocol<CharType>
|
||||
StringType& str;
|
||||
};
|
||||
|
||||
/// @brief output sink forwarding to a type-erased output adapter
|
||||
///
|
||||
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
|
||||
/// also target arbitrary adapters (output streams, strings, user-provided
|
||||
/// adapters) via the `output_adapter`-based overloads. Each write still goes
|
||||
/// through one virtual call, exactly as before; only the concrete sinks above
|
||||
/// avoid it.
|
||||
template<typename CharType>
|
||||
class output_adapter_sink
|
||||
{
|
||||
public:
|
||||
explicit output_adapter_sink(output_adapter_t<CharType> adapter)
|
||||
: oa(std::move(adapter))
|
||||
{
|
||||
JSON_ASSERT(oa);
|
||||
}
|
||||
|
||||
void write_character(CharType c)
|
||||
{
|
||||
oa->write_character(c);
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
|
||||
// the type-erased path already did before this sink existed
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
oa->write_characters(s, length);
|
||||
}
|
||||
|
||||
private:
|
||||
output_adapter_t<CharType> oa;
|
||||
};
|
||||
|
||||
template<typename CharType, typename StringType = std::basic_string<CharType>>
|
||||
class output_adapter
|
||||
{
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
#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>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
@@ -57,8 +59,6 @@ class serializer
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
using binary_char_t = typename BasicJsonType::binary_t::value_type;
|
||||
static constexpr std::uint8_t UTF8_ACCEPT = 0;
|
||||
static constexpr std::uint8_t UTF8_REJECT = 1;
|
||||
|
||||
public:
|
||||
/*!
|
||||
@@ -133,7 +133,7 @@ class serializer
|
||||
|
||||
Serializing a container descends into its elements, so a value nested deeply
|
||||
enough used to exhaust the call stack and terminate the process with no
|
||||
exception to catch. The descent is bounded here: once @ref dump_depth_limit
|
||||
exception to catch. The descent is bounded here: once @ref recursion_depth_limit
|
||||
levels have been entered, @ref dump_iteratively writes out what is left
|
||||
without the call stack. A value nested less deeply than that - all but a
|
||||
vanishing minority - is written by exactly the code that always wrote it.
|
||||
@@ -148,7 +148,7 @@ class serializer
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= dump_depth_limit()))
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
dump_iteratively(val, current_indent);
|
||||
return;
|
||||
@@ -223,7 +223,7 @@ class serializer
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= dump_depth_limit()))
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
dump_iteratively(val, current_indent);
|
||||
return;
|
||||
@@ -408,19 +408,12 @@ class serializer
|
||||
}
|
||||
|
||||
private:
|
||||
/// the number of levels @ref dump_internal descends into before it hands
|
||||
/// over to @ref dump_iteratively
|
||||
static constexpr std::size_t dump_depth_limit()
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write out @a val and everything below it without the call stack
|
||||
|
||||
Emits the same bytes as @ref dump_internal, keeping the containers it has
|
||||
entered on an explicit stack instead of descending into them. Only reached
|
||||
for values nested deeper than @ref dump_depth_limit, which is why it is not
|
||||
for values nested deeper than @ref recursion_depth_limit, which is why it is not
|
||||
written for speed: walking every value this way measured up to 20% slower on
|
||||
object-heavy documents than letting the compiler drive the descent.
|
||||
*/
|
||||
@@ -1061,7 +1054,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.back() | 0))), nullptr));
|
||||
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));
|
||||
}
|
||||
|
||||
case error_handler_t::ignore:
|
||||
@@ -1322,6 +1315,19 @@ class serializer
|
||||
pos += 6;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief convert a single element of a binary value to its byte value
|
||||
|
||||
The elements of a binary value are dumped as the numbers 0..255, regardless
|
||||
of the value type of the configured BinaryType: that type may be signed
|
||||
(`char`), unsigned (`std::uint8_t`), or not an integer at all
|
||||
(`std::byte`), none of which @ref dump_integer can handle uniformly.
|
||||
*/
|
||||
static std::uint8_t to_byte_value(binary_char_t x) noexcept
|
||||
{
|
||||
return static_cast<std::uint8_t>(x);
|
||||
}
|
||||
|
||||
// templates to avoid warnings about useless casts
|
||||
template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>
|
||||
bool is_negative_number(NumberType x)
|
||||
@@ -1343,8 +1349,10 @@ class serializer
|
||||
an arbitrary number, and the three digits it takes at most are written
|
||||
straight into the write buffer.
|
||||
|
||||
Any byte type that is not a plain unsigned byte is left to @ref dump_integer,
|
||||
whose representation of it may differ.
|
||||
Any byte type that is not a plain unsigned byte is converted to its
|
||||
@ref to_byte_value "byte value" and left to @ref dump_integer, so a signed
|
||||
or non-integral BinaryType::value_type (`char`, `std::byte`, ...) still
|
||||
dumps as 0..255.
|
||||
*/
|
||||
template<typename ByteType>
|
||||
void dump_byte(const ByteType value)
|
||||
@@ -1357,7 +1365,7 @@ class serializer
|
||||
template<typename ByteType>
|
||||
void dump_byte(const ByteType value, std::false_type /*is_plain_byte*/)
|
||||
{
|
||||
dump_integer(value);
|
||||
dump_integer(to_byte_value(value));
|
||||
}
|
||||
|
||||
template<typename ByteType>
|
||||
@@ -1403,8 +1411,7 @@ class serializer
|
||||
template < typename NumberType, detail::enable_if_t <
|
||||
std::is_integral<NumberType>::value ||
|
||||
std::is_same<NumberType, number_unsigned_t>::value ||
|
||||
std::is_same<NumberType, number_integer_t>::value ||
|
||||
std::is_same<NumberType, binary_char_t>::value,
|
||||
std::is_same<NumberType, number_integer_t>::value,
|
||||
int > = 0 >
|
||||
void dump_integer(NumberType x)
|
||||
{
|
||||
@@ -1584,62 +1591,6 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check whether a string is UTF-8 encoded
|
||||
|
||||
The function checks each byte of a string whether it is UTF-8 encoded. The
|
||||
result of the check is stored in the @a state parameter. The function must
|
||||
be called initially with state 0 (accept). State 1 means the string must
|
||||
be rejected, because the current byte is not allowed. If the string is
|
||||
completely processed, but the state is non-zero, the string ended
|
||||
prematurely; that is, the last byte indicated more bytes should have
|
||||
followed.
|
||||
|
||||
@param[in,out] state the state of the decoding
|
||||
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
|
||||
@param[in] byte next byte to decode
|
||||
@return new state
|
||||
|
||||
@note The function has been edited: a std::array is used.
|
||||
|
||||
@copyright Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
|
||||
@sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
*/
|
||||
static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept
|
||||
{
|
||||
static const std::array<std::uint8_t, 400> utf8d =
|
||||
{
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
|
||||
0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
|
||||
0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
|
||||
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
|
||||
}
|
||||
};
|
||||
|
||||
JSON_ASSERT(static_cast<std::size_t>(byte) < utf8d.size());
|
||||
const std::uint8_t type = utf8d[byte];
|
||||
|
||||
codep = (state != UTF8_ACCEPT)
|
||||
? (byte & 0x3fu) | (codep << 6u)
|
||||
: (0xFFu >> type) & (byte);
|
||||
|
||||
const std::size_t index = 256u + (static_cast<size_t>(state) * 16u) + static_cast<size_t>(type);
|
||||
JSON_ASSERT(index < utf8d.size());
|
||||
state = utf8d[index];
|
||||
return state;
|
||||
}
|
||||
|
||||
/*
|
||||
* Overload to make the compiler happy while it is instantiating
|
||||
* dump_integer for number_unsigned_t.
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
|
||||
/*!
|
||||
@brief the number of nesting levels an operation recurses into
|
||||
|
||||
Operations that walk a value (serializing, hashing, merging, ...) recurse once
|
||||
per nesting level, which is fastest, but a value nested deeply enough would
|
||||
exhaust the call stack. So they recurse only this many levels deep and finish
|
||||
whatever lies below with an explicit stack. All of them share this limit.
|
||||
|
||||
@sa https://github.com/nlohmann/json/issues/5387
|
||||
*/
|
||||
constexpr std::size_t recursion_depth_limit() noexcept
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -8,50 +8,56 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
|
||||
/*!
|
||||
@brief replace all occurrences of a substring by another string
|
||||
|
||||
@param[in,out] s the string to manipulate; changed so that all
|
||||
occurrences of @a f are replaced with @a t
|
||||
@param[in] f the substring to replace with @a t
|
||||
@param[in] t the string to replace @a f
|
||||
|
||||
@pre The search string @a f must not be empty. **This precondition is
|
||||
enforced with an assertion.**
|
||||
|
||||
@since version 2.0.0
|
||||
*/
|
||||
template<typename StringType>
|
||||
inline void replace_substring(StringType& s, const StringType& f,
|
||||
const StringType& t)
|
||||
{
|
||||
JSON_ASSERT(!f.empty());
|
||||
for (auto pos = s.find(f); // find the first occurrence of f
|
||||
pos != StringType::npos; // make sure f was found
|
||||
s.replace(pos, f.size(), t), // replace with t, and
|
||||
pos = s.find(f, pos + t.size())) // find the next occurrence of f
|
||||
{}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief string escaping as described in RFC 6901 (Sect. 4)
|
||||
* @param[in] s string to escape
|
||||
* @return escaped string
|
||||
*
|
||||
* Note the order of escaping "~" to "~0" and "/" to "~1" is important.
|
||||
*
|
||||
* The string is rebuilt in a single pass, appending whole runs between the
|
||||
* characters that need escaping. Scanning with find_first_of() keeps the
|
||||
* common case -- nothing to escape -- as fast as a single search, while
|
||||
* repeated replace() calls would move the tail of the string once per
|
||||
* escaped character.
|
||||
*/
|
||||
template<typename StringType>
|
||||
inline StringType escape(StringType s)
|
||||
inline StringType escape(const StringType& s)
|
||||
{
|
||||
replace_substring(s, StringType{"~"}, StringType{"~0"});
|
||||
replace_substring(s, StringType{"/"}, StringType{"~1"});
|
||||
return s;
|
||||
auto next_special = [&s](std::size_t from)
|
||||
{
|
||||
const auto tilde = s.find_first_of('~', from);
|
||||
const auto slash = s.find_first_of('/', from);
|
||||
return tilde < slash ? tilde : slash; // npos is the largest value
|
||||
};
|
||||
|
||||
auto pos = next_special(0);
|
||||
if (pos == StringType::npos)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
StringType result;
|
||||
result.reserve(s.size() + 2);
|
||||
|
||||
std::size_t run = 0;
|
||||
while (pos != StringType::npos)
|
||||
{
|
||||
result.append(s.data() + run, pos - run);
|
||||
result.append(s[pos] == '~' ? "~0" : "~1", 2);
|
||||
run = pos + 1;
|
||||
pos = next_special(run);
|
||||
}
|
||||
result.append(s.data() + run, s.size() - run);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -60,12 +66,43 @@ inline StringType escape(StringType s)
|
||||
* @return unescaped string
|
||||
*
|
||||
* Note the order of escaping "~1" to "/" and "~0" to "~" is important.
|
||||
*
|
||||
* Rebuilt in a single pass, see @ref escape. A "~" that is followed by
|
||||
* neither "0" nor "1" is passed through unchanged; @ref json_pointer rejects
|
||||
* such input before it gets here.
|
||||
*/
|
||||
template<typename StringType>
|
||||
inline void unescape(StringType& s)
|
||||
{
|
||||
replace_substring(s, StringType{"~1"}, StringType{"/"});
|
||||
replace_substring(s, StringType{"~0"}, StringType{"~"});
|
||||
auto pos = s.find_first_of('~', 0);
|
||||
if (pos == StringType::npos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StringType result;
|
||||
result.reserve(s.size());
|
||||
|
||||
std::size_t run = 0;
|
||||
while (pos != StringType::npos)
|
||||
{
|
||||
result.append(s.data() + run, pos - run);
|
||||
|
||||
const auto next = pos + 1;
|
||||
if (next < s.size() && (s[next] == '0' || s[next] == '1'))
|
||||
{
|
||||
result.append(s[next] == '0' ? "~" : "/", 1);
|
||||
run = pos + 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.append("~", 1);
|
||||
run = pos + 1;
|
||||
}
|
||||
pos = s.find_first_of('~', run);
|
||||
}
|
||||
result.append(s.data() + run, s.size() - run);
|
||||
s = result;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -8,10 +8,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <string> // string, to_string
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -33,5 +36,102 @@ StringType to_string(std::size_t value)
|
||||
return result;
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// UTF-8 decoding //
|
||||
///////////////////
|
||||
|
||||
// UTF-8 decoder states used by decode() below
|
||||
static constexpr std::uint8_t UTF8_ACCEPT = 0;
|
||||
static constexpr std::uint8_t UTF8_REJECT = 1;
|
||||
|
||||
/*!
|
||||
@brief process a byte of a UTF-8 sequence
|
||||
|
||||
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).
|
||||
|
||||
@param[in,out] state the current decoder state
|
||||
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
|
||||
@param[in] byte next byte to decode
|
||||
@return new state
|
||||
|
||||
@note Original source: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
@sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
*/
|
||||
inline std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept
|
||||
{
|
||||
static const std::array<std::uint8_t, 400> utf8d =
|
||||
{
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
|
||||
0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
|
||||
0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
|
||||
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
|
||||
}
|
||||
};
|
||||
|
||||
JSON_ASSERT(static_cast<std::size_t>(byte) < utf8d.size());
|
||||
const std::uint8_t type = utf8d[byte];
|
||||
|
||||
codep = (state != UTF8_ACCEPT)
|
||||
? (byte & 0x3fu) | (codep << 6u)
|
||||
: (0xFFu >> type) & (byte);
|
||||
|
||||
const std::size_t index = 256u + (static_cast<std::size_t>(state) * 16u) + static_cast<std::size_t>(type);
|
||||
JSON_ASSERT(index < utf8d.size());
|
||||
state = utf8d[index];
|
||||
return state;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check whether a string consists solely of valid UTF-8
|
||||
|
||||
Used by the CBOR/MessagePack/BSON/UBJSON binary readers to reject text
|
||||
strings that are not valid UTF-8 at decode time (RFC 8949 §3.1 and the
|
||||
MessagePack/BSON specifications all require text strings to be UTF-8), so
|
||||
that malformed input is caught immediately instead of only surfacing later
|
||||
as a type_error.316 when the resulting value is dumped.
|
||||
|
||||
@param[in] s the string to check
|
||||
@param[in] first index of the first byte to check; the bytes before it are
|
||||
assumed to have been validated already and to end on a
|
||||
code point boundary
|
||||
@return whether @a s (from index @a first on) is valid UTF-8
|
||||
*/
|
||||
template<typename StringType>
|
||||
inline bool is_valid_utf8(const StringType& s, const std::size_t first = 0) noexcept
|
||||
{
|
||||
std::uint8_t state = UTF8_ACCEPT;
|
||||
std::uint32_t codepoint = 0;
|
||||
|
||||
for (std::size_t i = first; i < s.size(); ++i)
|
||||
{
|
||||
decode(state, codepoint, static_cast<std::uint8_t>(s[i]));
|
||||
if (state == UTF8_REJECT)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return state == UTF8_ACCEPT;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
+1184
-151
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user