* Add json_document and json_view: node index, parser, and document Add json_document and json_view, a read-only, zero-copy index of a JSON text, as the first public slice of the zero-copy view (#5295). A parse produces a flat array of 16-byte nodes in document order, one per value and one per object key. Strings stay in the source text; escaped strings are decoded into an arena. Integers are converted while their digits are in the cache; floats keep only their digit layout and are converted on read. Containers store the size of their subtree, so a reader can step over one in constant time. A document makes a handful of allocations, however many values it has. The parser accepts exactly what json::parse accepts, with every combination of ignore_comments and ignore_trailing_commas, with and without a trailing NUL, and under JSON_STRICT_NUL_HANDLING. It is portable C++11 and does not depend on byte order. basic_json_document adds parse, parse_copy, accept, read (reuses a document's memory), root, is_discarded, source, owns_source, node_count, memory_usage, and shrink_to_fit. basic_json_view adds type, the is_* queries, operator bool, size, empty, materialize, and source_offset. A parse error throws the same exception basic_json::parse would throw for the same input, message and position included. detail::abi_config keeps JSON_STRICT_NUL_HANDLING and JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON readable after json.hpp undefines them, in the ABI namespace so they always match the basic_json in use. A NUL byte that ends a // comment is the end of the input, as in parse() since #5696. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix document_data for old Clang (3.4-3.6) Drop the empty braced NSDMIs of the std::string members: old Clang rejects the defaulted constructor when it is used by a member initializer before the end of the class definition. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Exclude libstdc++'s string_view comparison from the sign-change check basic_string_view::_S_compare stores the difference of two lengths in a signed difference_type on purpose, which -fsanitize=integer reports for every comparison with a shorter view (test-json_view_cpp17). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Reject integer lengths in json_document::parse parse(ptr, len) compiled: len converted to allow_exceptions, and ptr was read as a C string, past the end of a buffer without a terminating NUL. Delete the overloads of parse, parse_copy, accept, and read that take an integer other than bool where the flags are expected. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Copy const rvalue strings and document the accepted inputs json_document::parse(std::move(const_string)) failed to compile with "no matching read_kind": only a non-const rvalue std::string can be moved from. Treat a const rvalue as a copied byte container. parse() does not accept everything BasicJsonType::parse() does: a FILE* and pointers to or arrays of wide characters are rejected at compile time. List the supported inputs instead. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remove the conversion to bool from json_view explicit operator bool meant "refers to a value", which silently differs from what a basic_json converts to. Use !v.is_discarded() instead. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Delete json_document::root() on temporary documents auto v = json_document::parse(text).root() compiled and left the view dangling. Delete the overload for rvalue documents, take a named document in the tests, and document the lifetime rule. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Store node integers by halves on big-endian targets The non-little-endian path of the node writer wrote the integer's native word over len and next, so len got the high half there. Compose and split the value explicitly (len is the low half, next the high half); the little-endian path stays a plain memcpy. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Check the node array size for overflow reserve(n) and the growth of the index computed n * sizeof(node) without a check, which wraps around on 32-bit targets for inputs of about 1 GiB and allocates a too small array. Throw std::bad_alloc for a count beyond the address space and clamp the growth step to it. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * State the exact input size limit of json_document The check rejects inputs of 0xFFFFFFF0 bytes or more, but the exception message and the documentation said 4 GiB. Name the limit once (max_input_size), and state 4 GiB minus 16 bytes in the message and the documentation. Test the limit with a container that only claims the size. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Remove the unused legacy_discarded_value_comparison from abi_config Nothing reads it: the view does not compare values. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Align the banner comment of json_view.hpp Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fuzz json_document with exact-size buffers and parse options The fuzzer only parsed a std::string with default options. Also parse an exact-size byte vector, which has no NUL after its last byte and takes the bounds-checked path, and derive ignore_comments and ignore_trailing_commas from the first input byte, comparing against json::parse and json::accept with the same options. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Give document_data a user-provided constructor Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix clang-tidy findings in unit-json_view_builder.cpp Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Annotate intentional patterns in unit-json_view.cpp for clang-tidy Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Suppress Flawfinder's read() finding on the deleted overload Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Check the steps of the deep-nesting test separately Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Make max_input_size a function (GCC -Wunused-const-variable) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Make json_view.hpp pass include-what-you-use ci_single_binaries runs IWYU with --error on every header. For json_view.hpp it suggested adding <array> (std::array is used), the headers that json.hpp already provides (abi_config, abi_macros, input_adapters, json_pointer, cpp_future, string_concat, value_t, json_fwd), and <version> for std::nullptr_t, and removing <cstddef>. - include <array> - keep <cstddef> (nullptr_t, size_t; IWYU attributes them to <version> and <cstring>) - tell IWYU not to suggest the headers that json.hpp provides: the amalgamated json_view.hpp only includes json.hpp, so including them here would duplicate their definitions - export json.hpp, keep macro_unscope.hpp, and drop the unused forward declaration of the document class Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Read the node array base after emit() in the view builder's open() emit() moves the node array when it grows, and the subtraction read base in the same expression, so the order was unspecified. MSVC Release builds without forced inlining read the old base; the container index then pointed outside the array and close() wrote out of bounds. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Silence a clang-tidy use-after-move finding in the json_view test const_text is const, so std::move does not move from it; the second call is part of the const rvalue test. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Regenerate the amalgamated headers Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address the review of the view builder and document Changes promised in the replies to the review of #5620: - read() reuses the owned copy: a copied input (an rvalue byte container, a stream, FILE*, wide characters, an iterator range) is appended to the cleared owned buffer instead of a fresh std::string being move-assigned over it. A moved-in std::string still replaces it. - The error replay runs the library parser with a SAX handler that stores nothing and throws the reported exception, instead of BasicJsonType::parse(), which built a DOM up to the error. - Pointers to and arrays of wide characters, and FILE*, take the input adapter path (converted to UTF-8 into an owned buffer) instead of failing a static_assert. Wide pointers are NUL-terminated. - Streams are read in blocks through rdbuf() into the buffer, instead of character by character through the input adapter; eofbit is set at the end, as by the library's stream adapter. - tape/tape_size/tape_cap are now nodes/node_count/node_capacity. - The builder's members get descriptive, m_-prefixed names. - The shallow frame stack is a std::array (in a wrapper with a user-provided constructor: -Weffc++ wants the member initialized, and value-initializing the 64 frames costs up to 30 % on small documents). - The value dispatch macro has no NEXT parameter; the switch is left with break, and the macro is undefined after its last use. - literal() and digits_exceed() deduce the length from the string literal; each literal has one helper; the two `if (!is_float)` blocks are merged. - arena_ensure() sits right before the writes; the decode arena is a std::vector<char>. - The builder records the deepest nesting, and materialize() sizes its stack from it once (growing it only if a subtree is deeper). - Comment fixes (hex4, slow_string, the NUL rule, put_utf8, 0 - m, the arena resize, grow()) and formatting (macro backslashes, digit cases, one power of ten per line, value_t assertion next to is_container()). - build_with's template parameters follow the builder's order. Greg's idea of keeping the source offset in `off` and the arena offset in `next` for escaped strings was measured and not adopted: string access got 7-18 % slower with a select, and 3.5-18 % slower with a branch-free field pick (Apple M-series, clang -O3), parsing was unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Apply the review's wording fixes to the json_view documentation - Copying is disabled because of its cost, not because of two documents borrowing the same buffer; the unique_ptr follows from move-only. - Inputs: streams, FILE*, and wide characters (strings, arrays, pointers) are all supported and owned; point to parse_copy() for owned copies. - The error replay only runs for failing input, builds no values, and is skipped with allow_exceptions = false. - read() writes copied inputs into the previous owned copy. - Views can dangle like std::string_view, and why there is no shared/weak pointer design. - The 4 GiB limit (16-byte nodes, 16-byte scanner steps), streams being read to the end first, which macros apply, why materialize() has no source positions, and wording nits (accept() vs basic_json::accept(), is_binary, is_discarded, see-also lists, size table, "back into"). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Say that json_view converts integers while parsing Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address the review: null inline nodes, trait spelling, and max() in json_view - inline_nodes is null when the document has no inline nodes, instead of pointing past the end of the allocation; shrink_to_fit() no longer passes it to memcpy() for an empty node array - is_byte_char and is_wide_char use conjunction/disjunction, which astyle leaves unspaced - append_stream() uses (std::max) for the count read Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Include materialize.hpp in the view builder test The test calls detail::view::materialize(), which only the single header pulled in; the multiple-header build (JSON_TEST_USING_MULTIPLE_HEADERS) failed with "'materialize' is not a member of detail::view". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Rename emit() in the view builder for Qt compatibility Qt defines emit as an empty macro, so a Qt header included before json_view.hpp broke the declaration of the builder's emit(), see issue #5865. It is now called push_node(). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Do not copy an empty run into an unallocated arena memcpy() must not be passed a null pointer even for zero bytes, which arena_run() did for an empty run while the arena was still empty (ci_test_clang_sanitizer: "null pointer passed as argument 1" in builder.hpp). Only the slow path can see such a run. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Exclude the view builder test from Infer's reports Infer's std::unique_ptr model confuses the values that the test's build() helper returns by value and reports USE_AFTER_DELETE for one of them after the destructor of an unrelated temporary (ci_infer, 12 reports, all in tests/src/unit-json_view_builder.cpp). AddressSanitizer is clean on the same tests. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Skip the deleted-overload checks of json_view on MSVC 2015 MSVC 2015 does not treat selecting a deleted function in decltype as a substitution failure, so is_detected reports the deleted parse/parse_copy/accept/read(ptr, len) overloads as callable and the static_asserts in "integer arguments do not compile" fail (AppVeyor VS2015 jobs). Calling them still fails to compile there. Guard the negative checks as unit-delete_deprecated_functions.cpp does. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
686 lines
26 KiB
C++
686 lines
26 KiB
C++
// __ _____ _____ _____
|
|
// __| | __| | | | 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
|
|
|
|
/****************************************************************************\
|
|
* Zero-copy, read-only view of a parsed JSON text. *
|
|
* *
|
|
* json_document::parse() builds a flat index of the values of a JSON text *
|
|
* (16 bytes per value) instead of a tree of basic_json values. Strings and *
|
|
* numbers stay in the source text; only strings with escapes are decoded, *
|
|
* into one buffer. json_view is a handle to one value of the document, with *
|
|
* the read-only part of the basic_json interface; materialize() turns a *
|
|
* subtree into the basic_json value that parse() would produce. *
|
|
* *
|
|
* The source text must outlive a document that borrows it (lvalue byte *
|
|
* containers, C strings); rvalue strings, streams, and other inputs are *
|
|
* owned by the document. *
|
|
\****************************************************************************/
|
|
|
|
#ifndef INCLUDE_NLOHMANN_JSON_VIEW_HPP_
|
|
#define INCLUDE_NLOHMANN_JSON_VIEW_HPP_
|
|
|
|
#include <array> // array
|
|
#include <cstddef> // nullptr_t, size_t // IWYU pragma: keep
|
|
#include <cstring> // memcpy, strlen
|
|
#include <istream> // istream
|
|
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
|
#include <memory> // unique_ptr
|
|
#include <string> // string
|
|
#include <type_traits> // decay, enable_if, integral_constant, is_base_of, is_integral, is_same, remove_cv, remove_extent
|
|
#include <utility> // forward, move
|
|
#include <vector> // vector
|
|
|
|
#include <nlohmann/json.hpp> // IWYU pragma: export
|
|
|
|
// json.hpp provides the library's types and macros used below (it includes
|
|
// the headers that define them); json_view.hpp must not include them again,
|
|
// because the amalgamated json_view.hpp only includes json.hpp
|
|
// IWYU pragma: no_include <version>
|
|
// IWYU pragma: no_include "nlohmann/detail/abi_config.hpp"
|
|
// IWYU pragma: no_include "nlohmann/detail/abi_macros.hpp"
|
|
// IWYU pragma: no_include "nlohmann/detail/input/input_adapters.hpp"
|
|
// IWYU pragma: no_include "nlohmann/detail/json_pointer.hpp"
|
|
// IWYU pragma: no_include "nlohmann/detail/meta/cpp_future.hpp"
|
|
// IWYU pragma: no_include "nlohmann/detail/string_concat.hpp"
|
|
// IWYU pragma: no_include "nlohmann/detail/value_t.hpp"
|
|
// IWYU pragma: no_include "nlohmann/json.hpp"
|
|
// IWYU pragma: no_include "nlohmann/json_fwd.hpp"
|
|
|
|
// the view builds on internals of the library: both must be the same version
|
|
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0
|
|
#error "json_view.hpp requires json.hpp of the same version (3.12.0)"
|
|
#endif
|
|
|
|
#include <nlohmann/detail/view/builder.hpp>
|
|
#include <nlohmann/detail/view/document_data.hpp>
|
|
#include <nlohmann/detail/view/errors.hpp>
|
|
#include <nlohmann/detail/view/input.hpp>
|
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
|
#include <nlohmann/detail/view/materialize.hpp>
|
|
#include <nlohmann/detail/view/node.hpp>
|
|
#include <nlohmann/detail/view/string_ref.hpp>
|
|
|
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
|
|
/*!
|
|
@brief read-only handle to one value of a basic_json_document
|
|
|
|
Trivially copyable (two pointers). Valid as long as the document is alive and
|
|
has not been re-parsed, and as long as a borrowed source text is alive.
|
|
*/
|
|
template<typename BasicJsonType>
|
|
class basic_json_view
|
|
{
|
|
using node = detail::view::node;
|
|
using document_data = detail::view::document_data;
|
|
|
|
public:
|
|
using value_t = detail::value_t;
|
|
using string_t = typename BasicJsonType::string_t;
|
|
using number_integer_t = typename BasicJsonType::number_integer_t;
|
|
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
|
using number_float_t = typename BasicJsonType::number_float_t;
|
|
using json_pointer = typename BasicJsonType::json_pointer;
|
|
using size_type = std::size_t;
|
|
/// std::string_view from C++17 on
|
|
using string_view_t = detail::view::string_ref;
|
|
|
|
/// an invalid view (type() == value_t::discarded)
|
|
basic_json_view() noexcept = default;
|
|
|
|
//////////
|
|
// type //
|
|
//////////
|
|
|
|
NLOHMANN_VIEW_ALWAYS_INLINE value_t type() const noexcept
|
|
{
|
|
return m_node != nullptr ? static_cast<value_t>(m_node->kind) : value_t::discarded;
|
|
}
|
|
|
|
bool is_null() const noexcept
|
|
{
|
|
return type() == value_t::null;
|
|
}
|
|
|
|
bool is_boolean() const noexcept
|
|
{
|
|
return type() == value_t::boolean;
|
|
}
|
|
|
|
bool is_number() const noexcept
|
|
{
|
|
return is_number_integer() || is_number_float();
|
|
}
|
|
|
|
bool is_number_integer() const noexcept
|
|
{
|
|
return type() == value_t::number_integer || type() == value_t::number_unsigned;
|
|
}
|
|
|
|
bool is_number_unsigned() const noexcept
|
|
{
|
|
return type() == value_t::number_unsigned;
|
|
}
|
|
|
|
bool is_number_float() const noexcept
|
|
{
|
|
return type() == value_t::number_float;
|
|
}
|
|
|
|
bool is_string() const noexcept
|
|
{
|
|
return type() == value_t::string;
|
|
}
|
|
|
|
bool is_array() const noexcept
|
|
{
|
|
return type() == value_t::array;
|
|
}
|
|
|
|
bool is_object() const noexcept
|
|
{
|
|
return type() == value_t::object;
|
|
}
|
|
|
|
/// always false: JSON text has no binary values
|
|
bool is_binary() const noexcept
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool is_primitive() const noexcept
|
|
{
|
|
return is_null() || is_string() || is_boolean() || is_number();
|
|
}
|
|
|
|
bool is_structured() const noexcept
|
|
{
|
|
return is_array() || is_object();
|
|
}
|
|
|
|
/// the root of a failed parse with allow_exceptions == false, or a
|
|
/// default-constructed view
|
|
bool is_discarded() const noexcept
|
|
{
|
|
return type() == value_t::discarded;
|
|
}
|
|
|
|
//////////////
|
|
// capacity //
|
|
//////////////
|
|
|
|
/// the number of elements (arrays, objects), 0 for null and discarded,
|
|
/// 1 otherwise, as basic_json::size()
|
|
size_type size() const noexcept
|
|
{
|
|
switch (type())
|
|
{
|
|
case value_t::null:
|
|
case value_t::discarded:
|
|
return 0;
|
|
case value_t::array:
|
|
case value_t::object:
|
|
return m_node->len;
|
|
case value_t::string:
|
|
case value_t::boolean:
|
|
case value_t::number_integer:
|
|
case value_t::number_unsigned:
|
|
case value_t::number_float:
|
|
case value_t::binary:
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/// as basic_json::empty()
|
|
bool empty() const noexcept
|
|
{
|
|
switch (type())
|
|
{
|
|
case value_t::null:
|
|
case value_t::discarded:
|
|
return true;
|
|
case value_t::array:
|
|
case value_t::object:
|
|
return m_node->len == 0;
|
|
case value_t::string:
|
|
case value_t::boolean:
|
|
case value_t::number_integer:
|
|
case value_t::number_unsigned:
|
|
case value_t::number_float:
|
|
case value_t::binary:
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/////////////////
|
|
// materialize //
|
|
/////////////////
|
|
|
|
/// the basic_json value of this subtree, as parse() would produce it
|
|
/// (a discarded value for a discarded view)
|
|
BasicJsonType materialize() const
|
|
{
|
|
if (m_node == nullptr)
|
|
{
|
|
return BasicJsonType(value_t::discarded);
|
|
}
|
|
return detail::view::materialize<BasicJsonType>(*m_doc, m_node);
|
|
}
|
|
|
|
/// byte offset of this value in the source text (for strings: of the
|
|
/// first byte after the opening quote); static_cast<std::size_t>(-1) for
|
|
/// a discarded view and for strings with escapes, which are decoded
|
|
std::size_t source_offset() const noexcept
|
|
{
|
|
return m_node != nullptr && (m_node->flags & detail::view::node_flags::storage) == 0
|
|
? m_node->off : static_cast<std::size_t>(-1);
|
|
}
|
|
|
|
private:
|
|
template<typename> friend class basic_json_document;
|
|
|
|
basic_json_view(const document_data* d, const node* n) noexcept
|
|
: m_doc(d), m_node(n)
|
|
{}
|
|
|
|
const document_data* m_doc = nullptr;
|
|
const node* m_node = nullptr;
|
|
};
|
|
|
|
/*!
|
|
@brief a parsed JSON text: owns the node index (and, optionally, the text)
|
|
|
|
Borrowed parses keep a pointer to the caller's text, which must outlive the
|
|
document. Owned parses (parse_copy, rvalue std::string, streams, and inputs
|
|
that are not contiguous byte ranges) keep their own copy.
|
|
*/
|
|
template<typename BasicJsonType>
|
|
class basic_json_document
|
|
{
|
|
using document_data = detail::view::document_data;
|
|
|
|
static_assert(sizeof(typename BasicJsonType::number_integer_t) == 8 && sizeof(typename BasicJsonType::number_unsigned_t) == 8,
|
|
"json_view supports 64-bit integer types only");
|
|
|
|
public:
|
|
using view_type = basic_json_view<BasicJsonType>;
|
|
using value_t = detail::value_t;
|
|
|
|
/// an empty (discarded) document
|
|
basic_json_document() = default;
|
|
basic_json_document(basic_json_document&&) noexcept = default;
|
|
basic_json_document& operator=(basic_json_document&&) noexcept = default;
|
|
basic_json_document(const basic_json_document&) = delete;
|
|
basic_json_document& operator=(const basic_json_document&) = delete;
|
|
~basic_json_document() = default;
|
|
|
|
/////////////
|
|
// parsing //
|
|
/////////////
|
|
|
|
/// parse a JSON text; contiguous byte inputs are borrowed, everything else
|
|
/// (and rvalue std::string) is owned
|
|
template<typename InputType>
|
|
NLOHMANN_VIEW_NODISCARD
|
|
static basic_json_document parse(InputType&& input,
|
|
const bool allow_exceptions = true,
|
|
const bool ignore_comments = false,
|
|
const bool ignore_trailing_commas = false)
|
|
{
|
|
basic_json_document d;
|
|
d.read(std::forward<InputType>(input), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
|
return d;
|
|
}
|
|
|
|
/// parse(ptr, len) does not compile: len would convert to allow_exceptions
|
|
/// and ptr be read as a C string (as for the overloads of parse_copy,
|
|
/// accept, and read below)
|
|
template<typename InputType, typename IntegerType, typename... Flags>
|
|
static typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, basic_json_document>::type
|
|
parse(InputType&& input, IntegerType value, Flags&&... flags) = delete;
|
|
|
|
/// parse [first, last)
|
|
template<typename IteratorType, typename std::enable_if<
|
|
std::is_base_of<std::input_iterator_tag, typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
|
|
NLOHMANN_VIEW_NODISCARD
|
|
static basic_json_document parse(IteratorType first, IteratorType last,
|
|
const bool allow_exceptions = true,
|
|
const bool ignore_comments = false,
|
|
const bool ignore_trailing_commas = false)
|
|
{
|
|
basic_json_document d;
|
|
d.read_range(first, last, allow_exceptions, ignore_comments, ignore_trailing_commas);
|
|
return d;
|
|
}
|
|
|
|
/// parse a copy of the input; the document does not depend on it afterwards
|
|
template<typename InputType>
|
|
NLOHMANN_VIEW_NODISCARD
|
|
static basic_json_document parse_copy(InputType&& input,
|
|
const bool allow_exceptions = true,
|
|
const bool ignore_comments = false,
|
|
const bool ignore_trailing_commas = false)
|
|
{
|
|
basic_json_document d;
|
|
std::string buf;
|
|
append_input(buf, std::forward<InputType>(input));
|
|
d.build_owned(std::move(buf), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
|
return d;
|
|
}
|
|
|
|
template<typename InputType, typename IntegerType, typename... Flags>
|
|
static typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, basic_json_document>::type
|
|
parse_copy(InputType&& input, IntegerType value, Flags&&... flags) = delete;
|
|
|
|
/// check whether the input is valid JSON (the result of basic_json::accept)
|
|
template<typename InputType>
|
|
static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false)
|
|
{
|
|
basic_json_document d;
|
|
d.read(std::forward<InputType>(input), false, ignore_comments, ignore_trailing_commas);
|
|
return !d.is_discarded();
|
|
}
|
|
|
|
template<typename InputType, typename IntegerType, typename... Flags>
|
|
static typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, bool>::type
|
|
accept(InputType&& input, IntegerType value, Flags&&... flags) = delete;
|
|
|
|
/// parse into this document, reusing its memory
|
|
template<typename InputType>
|
|
// flawfinder: ignore (a member function, not POSIX read())
|
|
void read(InputType&& input,
|
|
const bool allow_exceptions = true,
|
|
const bool ignore_comments = false,
|
|
const bool ignore_trailing_commas = false)
|
|
{
|
|
read_kind(std::forward<InputType>(input), allow_exceptions, ignore_comments, ignore_trailing_commas,
|
|
std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {});
|
|
}
|
|
|
|
template<typename InputType, typename IntegerType, typename... Flags>
|
|
typename std::enable_if<detail::view::is_integer_not_bool<IntegerType>::value, void>::type
|
|
// flawfinder: ignore (a member function, not POSIX read())
|
|
read(InputType&& input, IntegerType value, Flags&&... flags) = delete;
|
|
|
|
////////////
|
|
// access //
|
|
////////////
|
|
|
|
/// the root value (discarded if parsing failed without exceptions)
|
|
view_type root() const& noexcept
|
|
{
|
|
if (!m_data || m_data->discarded)
|
|
{
|
|
return view_type();
|
|
}
|
|
return view_type(m_data.get(), m_data->nodes);
|
|
}
|
|
|
|
/// deleted: the view of a temporary document would dangle
|
|
view_type root() const&& = delete;
|
|
|
|
bool is_discarded() const noexcept
|
|
{
|
|
return !m_data || m_data->discarded;
|
|
}
|
|
|
|
/// the parsed text
|
|
typename view_type::string_view_t source() const noexcept
|
|
{
|
|
return m_data ? typename view_type::string_view_t(m_data->src, m_data->size) : typename view_type::string_view_t();
|
|
}
|
|
|
|
/// whether the document holds its own copy of the text
|
|
bool owns_source() const noexcept
|
|
{
|
|
return m_data && !m_data->owned.empty() && m_data->src == m_data->owned.data();
|
|
}
|
|
|
|
/// number of index nodes (values plus object keys)
|
|
std::size_t node_count() const noexcept
|
|
{
|
|
return m_data ? m_data->node_count : 0;
|
|
}
|
|
|
|
/// bytes held by the document (index, decoded strings, owned text)
|
|
std::size_t memory_usage() const noexcept
|
|
{
|
|
if (!m_data)
|
|
{
|
|
return 0;
|
|
}
|
|
return sizeof(document_data) + (m_data->inline_capacity * sizeof(detail::view::node))
|
|
+ (m_data->nodes != m_data->inline_nodes ? m_data->node_capacity * sizeof(detail::view::node) : 0)
|
|
+ m_data->arena.capacity() + m_data->owned.capacity();
|
|
}
|
|
|
|
/// release unused capacity of the index and the decoded strings; like
|
|
/// std::vector::shrink_to_fit, this invalidates the views of the document
|
|
/// (take new ones from root())
|
|
void shrink_to_fit()
|
|
{
|
|
if (!m_data)
|
|
{
|
|
return;
|
|
}
|
|
using detail::view::node;
|
|
document_data& d = *m_data;
|
|
|
|
// allocate everything first, so that an exception leaves the document
|
|
// unchanged
|
|
const bool shrink_arena = d.arena.capacity() > d.arena.size();
|
|
std::vector<char> arena(shrink_arena ? d.arena : std::vector<char>());
|
|
const bool shrink_nodes = d.nodes != d.inline_nodes && d.node_count != d.node_capacity;
|
|
const bool into_header = d.node_count <= d.inline_capacity;
|
|
node* fresh = (shrink_nodes && !into_header) ? static_cast<node*>(::operator new (d.node_count * sizeof(node))) : d.inline_nodes;
|
|
|
|
if (shrink_nodes)
|
|
{
|
|
if (d.node_count != 0)
|
|
{
|
|
std::memcpy(fresh, d.nodes, d.node_count * sizeof(node));
|
|
}
|
|
::operator delete (d.nodes);
|
|
d.nodes = fresh;
|
|
d.node_capacity = into_header ? d.inline_capacity : d.node_count;
|
|
}
|
|
if (shrink_arena)
|
|
{
|
|
d.arena.swap(arena);
|
|
d.base[1] = d.arena.data();
|
|
}
|
|
}
|
|
|
|
private:
|
|
using input_kind = detail::view::input_kind;
|
|
|
|
/// create the storage (sized for the input) on first use
|
|
void ensure_data(const char* src, std::size_t size)
|
|
{
|
|
if (!m_data)
|
|
{
|
|
m_data.reset(document_data::create(detail::view::estimate_nodes(src, size)));
|
|
}
|
|
}
|
|
|
|
/// parse a buffer the document takes ownership of (it replaces the
|
|
/// previous owned copy)
|
|
void build_owned(std::string&& buf, bool allow_exceptions, bool comments, bool trailing_commas)
|
|
{
|
|
ensure_data(buf.data(), buf.size());
|
|
m_data->owned = std::move(buf);
|
|
build(m_data->owned.data(), m_data->owned.size(), allow_exceptions, comments, trailing_commas, true, true);
|
|
}
|
|
|
|
/// parse a copy of the input; the copy is appended to the cleared owned
|
|
/// buffer, so that its capacity is reused
|
|
template<typename T>
|
|
void build_copy(T&& input, bool allow_exceptions, bool comments, bool trailing_commas)
|
|
{
|
|
if (!m_data)
|
|
{
|
|
std::string buf;
|
|
append_input(buf, std::forward<T>(input));
|
|
build_owned(std::move(buf), allow_exceptions, comments, trailing_commas);
|
|
return;
|
|
}
|
|
document_data& d = *m_data;
|
|
// (the previous text is gone from here on: should reading the input
|
|
// throw, the document is discarded)
|
|
d.discarded = true;
|
|
d.src = "";
|
|
d.size = 0;
|
|
d.owned.clear();
|
|
append_input(d.owned, std::forward<T>(input));
|
|
build(d.owned.data(), d.owned.size(), allow_exceptions, comments, trailing_commas, true, true);
|
|
}
|
|
|
|
/// sentinel: src[size] is readable and 0 (std::string, C strings)
|
|
void build(const char* src, std::size_t size, bool allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel)
|
|
{
|
|
ensure_data(src, size);
|
|
document_data& d = *m_data;
|
|
if (!owned)
|
|
{
|
|
d.owned.clear();
|
|
}
|
|
d.src = src;
|
|
d.size = size;
|
|
d.node_count = 0;
|
|
d.arena.clear();
|
|
d.discarded = true;
|
|
detail::view::parse_failure failure;
|
|
bool ok = false;
|
|
if (NLOHMANN_VIEW_UNLIKELY(size > detail::view::max_input_size()))
|
|
{
|
|
failure.code = detail::view::error_code::input_too_large;
|
|
}
|
|
else
|
|
{
|
|
ok = detail::view::build < typename BasicJsonType::number_float_t, !detail::abi_config::strict_nul_handling > (d, src, size, comments, trailing_commas, sentinel, failure);
|
|
}
|
|
if (NLOHMANN_VIEW_LIKELY(ok))
|
|
{
|
|
d.base[0] = d.src;
|
|
d.base[1] = d.arena.data();
|
|
d.discarded = false;
|
|
return;
|
|
}
|
|
if (allow_exceptions)
|
|
{
|
|
detail::view::throw_parse_failure<BasicJsonType>(failure, src, size, comments, trailing_commas);
|
|
}
|
|
}
|
|
|
|
// --- input dispatch (see detail::view::input_kind) ---
|
|
|
|
void read_kind(std::string&& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::move_string> /*unused*/)
|
|
{
|
|
build_owned(std::move(s), ae, c, tc);
|
|
}
|
|
|
|
template<typename CharT>
|
|
void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::c_string> /*unused*/)
|
|
{
|
|
if (s == nullptr)
|
|
{
|
|
build("", 0, ae, c, tc, false, true);
|
|
return;
|
|
}
|
|
const char* cs = reinterpret_cast<const char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
// C strings are null-terminated, as for json::parse(const char*)
|
|
// flawfinder: ignore
|
|
build(cs, std::strlen(cs), ae, c, tc, false, true);
|
|
}
|
|
|
|
template<typename Array>
|
|
void read_kind(Array& a, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::char_array> /*unused*/)
|
|
{
|
|
using CharT = typename std::remove_cv<typename std::remove_extent<Array>::type>::type;
|
|
const std::size_t n = std::extent<Array>::value;
|
|
// a trailing NUL (string literals) is not part of the text, as for
|
|
// parse(), and serves as sentinel
|
|
const bool terminated = n > 0 && a[n - 1] == 0 && (!detail::abi_config::strict_nul_handling || std::is_same<CharT, char>::value);
|
|
build(reinterpret_cast<const char*>(&a[0]), terminated ? n - 1 : n, ae, c, tc, false, terminated); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
}
|
|
|
|
template<typename T>
|
|
void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::borrow_range> /*unused*/)
|
|
{
|
|
// std::basic_string guarantees data()[size()] == 0: use it as sentinel
|
|
const auto size = static_cast<std::size_t>(s.size());
|
|
build(size == 0 ? "" : reinterpret_cast<const char*>(s.data()), size, ae, c, tc, false, // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
detail::view::is_std_string<T>::value || size == 0);
|
|
}
|
|
|
|
template<typename T>
|
|
void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::copy_range> /*unused*/)
|
|
{
|
|
build_copy(s, ae, c, tc);
|
|
}
|
|
|
|
template<typename CharT>
|
|
void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::wide_c_string> /*unused*/)
|
|
{
|
|
if (s == nullptr)
|
|
{
|
|
build("", 0, ae, c, tc, false, true);
|
|
return;
|
|
}
|
|
// NUL-terminated, as for the single-byte C strings
|
|
build_copy(detail::view::make_adapter_input(detail::input_adapter(s, s + std::char_traits<typename std::remove_cv<CharT>::type>::length(s))), ae, c, tc);
|
|
}
|
|
|
|
template<typename T>
|
|
void read_kind(T&& input, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::adapter> /*unused*/)
|
|
{
|
|
build_copy(std::forward<T>(input), ae, c, tc);
|
|
}
|
|
|
|
template<typename IteratorType>
|
|
void read_range(IteratorType first, IteratorType last, bool ae, bool c, bool tc)
|
|
{
|
|
using value_type = typename std::remove_cv<typename std::iterator_traits<IteratorType>::value_type>::type;
|
|
// borrow the range where the library's input adapter scans it as one
|
|
// block: pointers and, in C++20, contiguous iterators (std::vector,
|
|
// std::string, ...) over single bytes
|
|
read_range_impl(first, last, ae, c, tc, std::integral_constant < bool, std::is_integral<value_type>::value
|
|
&& detail::iterator_input_adapter<IteratorType>::supports_bulk_scan > {});
|
|
}
|
|
|
|
template<typename IteratorType>
|
|
void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::true_type /*contiguous bytes*/)
|
|
{
|
|
const auto size = static_cast<std::size_t>(std::distance(first, last));
|
|
build(size == 0 ? "" : reinterpret_cast<const char*>(&*first), size, ae, c, tc, false, size == 0); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
}
|
|
|
|
template<typename IteratorType>
|
|
void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::false_type /*other*/)
|
|
{
|
|
build_copy(detail::view::make_adapter_input(detail::input_adapter(first, last)), ae, c, tc);
|
|
}
|
|
|
|
/// append the text of an input to buf: a contiguous byte input as one
|
|
/// block, a stream in blocks, an input adapter (made here for any other
|
|
/// input) character by character
|
|
template<typename T>
|
|
static void append_input(std::string& buf, T&& input)
|
|
{
|
|
using D = typename std::decay<T>::type;
|
|
// NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11
|
|
append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0
|
|
: std::is_base_of<std::istream, D>::value ? 1
|
|
: detail::view::is_adapter_input<D>::value ? 2 : 3 > {});
|
|
// NOLINTEND(readability-avoid-nested-conditional-operator)
|
|
}
|
|
|
|
template<typename T>
|
|
static void append_input_impl(std::string& buf, const T& s, std::integral_constant<int, 0> /*contiguous bytes*/)
|
|
{
|
|
buf.append(reinterpret_cast<const char*>(s.data()), static_cast<std::size_t>(s.size())); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
|
}
|
|
|
|
template<typename Stream>
|
|
static void append_input_impl(std::string& buf, Stream&& is, std::integral_constant<int, 1> /*stream*/) // NOLINT(cppcoreguidelines-missing-std-forward): read in place, as by the library's stream adapter
|
|
{
|
|
detail::view::append_stream(buf, is);
|
|
}
|
|
|
|
template<typename Adapter>
|
|
static void append_input_impl(std::string& buf, detail::view::adapter_input<Adapter>&& input, std::integral_constant<int, 2> /*input adapter*/)
|
|
{
|
|
detail::view::append_adapter(buf, std::move(input.ia));
|
|
}
|
|
|
|
template<typename T>
|
|
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
|
|
{
|
|
detail::view::append_adapter(buf, detail::input_adapter(std::forward<T>(input)));
|
|
}
|
|
|
|
std::unique_ptr<document_data, document_data::deleter> m_data{}; // NOLINT(readability-redundant-member-init)
|
|
};
|
|
|
|
/// a parsed JSON text for json
|
|
using json_document = basic_json_document<json>;
|
|
/// a value of a json_document
|
|
using json_view = basic_json_view<json>;
|
|
/// a parsed JSON text for ordered_json
|
|
using ordered_json_document = basic_json_document<ordered_json>;
|
|
/// a value of an ordered_json_document
|
|
using ordered_json_view = basic_json_view<ordered_json>;
|
|
|
|
NLOHMANN_JSON_NAMESPACE_END
|
|
|
|
#include <nlohmann/detail/view/macro_unscope.hpp> // IWYU pragma: keep
|
|
|
|
#endif // INCLUDE_NLOHMANN_JSON_VIEW_HPP_
|